slf4j.org.apache.commons.logging
source.. = src/,\
ext/test/
-bin.. = bin/
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
- <classpathentry kind="src" path="src/main/java"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>>>
- <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
- <classpathentry kind="output" path="target/classes"/>
+ <classpathentry kind="src" path="src" />
+ <classpathentry kind="con"
+ path="org.eclipse.pde.core.requiredPlugins" />
+ <classpathentry kind="con"
+ path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" />
+ <classpathentry kind="output" path="bin" />
</classpath>
--- /dev/null
+Fragment-Host: org.apache.jackrabbit
+Import-Package: org.springframework.core,\
+org.argeo.jcr,\
+*
-source.. = src/main/java/
-output.. = target/classes/
-bin.includes = META-INF/,\
- .
+source.. = src/
</parent>
<artifactId>org.argeo.security.jackrabbit</artifactId>
<name>Commons Security Jackrabbit</name>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-source-plugin</artifactId>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jar-plugin</artifactId>
- </plugin>
- <plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-bundle-plugin</artifactId>
-
- <configuration>
- <instructions>
- <Fragment-Host>org.apache.jackrabbit</Fragment-Host>
- <Export-Package>org.argeo.security.jackrabbit.*</Export-Package>
- <Import-Package>
- org.springframework.core,
- org.argeo.jcr,
- *</Import-Package>
- </instructions>
- </configuration>
- </plugin>
- </plugins>
- </build>
<dependencies>
<dependency>
<groupId>org.argeo.commons</groupId>
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jackrabbit;
-
-import javax.jcr.PathNotFoundException;
-import javax.jcr.RepositoryException;
-import javax.jcr.security.Privilege;
-
-import org.apache.jackrabbit.core.id.ItemId;
-import org.apache.jackrabbit.core.security.DefaultAccessManager;
-import org.apache.jackrabbit.spi.Path;
-
-/**
- * Intermediary class in order to have a consistent naming in config files. Does
- * nothing for the time being, but may in the future.
- */
-public class ArgeoAccessManager extends DefaultAccessManager {
-
- @Override
- public boolean canRead(Path itemPath, ItemId itemId)
- throws RepositoryException {
- return super.canRead(itemPath, itemId);
- }
-
- @Override
- public Privilege[] getPrivileges(String absPath)
- throws PathNotFoundException, RepositoryException {
- return super.getPrivileges(absPath);
- }
-
- @Override
- public boolean hasPrivileges(String absPath, Privilege[] privileges)
- throws PathNotFoundException, RepositoryException {
- return super.hasPrivileges(absPath, privileges);
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jackrabbit;
-
-import java.security.Principal;
-import java.security.acl.Group;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.Credentials;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.LoginException;
-
-import org.apache.jackrabbit.core.security.AnonymousPrincipal;
-import org.apache.jackrabbit.core.security.authentication.AbstractLoginModule;
-import org.apache.jackrabbit.core.security.authentication.Authentication;
-import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
-import org.argeo.security.SystemAuthentication;
-import org.springframework.security.GrantedAuthority;
-import org.springframework.security.context.SecurityContextHolder;
-import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
-
-/** Jackrabbit login mechanism based on Spring Security */
-public class ArgeoLoginModule extends AbstractLoginModule {
- private String adminRole = "ROLE_ADMIN";
-
- @SuppressWarnings("unused")
- @Override
- public boolean login() throws LoginException {
- boolean loginOk = super.login();
- if (!loginOk) {
- org.springframework.security.Authentication authen = (org.springframework.security.Authentication) SecurityContextHolder
- .getContext().getAuthentication();
- }
- return loginOk;
- }
-
- @SuppressWarnings("unused")
- @Override
- public boolean commit() throws LoginException {
- boolean commitOk = super.commit();
- if (!commitOk) {
- org.springframework.security.Authentication authen = (org.springframework.security.Authentication) SecurityContextHolder
- .getContext().getAuthentication();
- }
- return commitOk;
- }
-
- /**
- * Returns the Spring {@link org.springframework.security.Authentication}
- * (which can be null)
- */
- @Override
- protected Principal getPrincipal(Credentials credentials) {
- org.springframework.security.Authentication authen = SecurityContextHolder
- .getContext().getAuthentication();
- return authen;
- }
-
- protected Set<Principal> getPrincipals() {
- // clear already registered Jackrabbit principals
- // clearPrincipals(AdminPrincipal.class);
- // clearPrincipals(AnonymousPrincipal.class);
- // clearPrincipals(GrantedAuthorityPrincipal.class);
-
- return syncPrincipals();
- }
-
- protected Set<Principal> syncPrincipals() {
- // use linked HashSet instead of HashSet in order to maintain the order
- // of principals (as in the Subject).
- org.springframework.security.Authentication authen = (org.springframework.security.Authentication) principal;
-
- Set<Principal> principals = new LinkedHashSet<Principal>();
- principals.add(authen);
-
- if (authen instanceof SystemAuthentication) {
- principals.add(new AdminPrincipal(authen.getName()));
- principals.add(new ArgeoSystemPrincipal(authen.getName()));
- } else if (authen instanceof AnonymousAuthenticationToken) {
- principals.add(new AnonymousPrincipal());
- } else {
- for (GrantedAuthority ga : authen.getAuthorities()) {
- principals.add(new GrantedAuthorityPrincipal(ga));
- // FIXME: make it more generic
- if (adminRole.equals(ga.getAuthority()))
- principals.add(new AdminPrincipal(authen.getName()));
- }
- }
-
- // remove previous credentials
- Set<SimpleCredentials> thisCredentials = subject
- .getPublicCredentials(SimpleCredentials.class);
- if (thisCredentials != null)
- thisCredentials.clear();
- // override credentials since we did not used the one passed to us
- // credentials = new SimpleCredentials(authen.getName(), authen
- // .getCredentials().toString().toCharArray());
-
- return principals;
- }
-
- /**
- * Super implementation removes all {@link Principal}, the Spring
- * {@link org.springframework.security.Authentication} as well. Here we
- * simply clear Jackrabbit related {@link Principal}s.
- */
- @Override
- public boolean logout() throws LoginException {
- clearPrincipals(AdminPrincipal.class);
- clearPrincipals(ArgeoSystemPrincipal.class);
- clearPrincipals(AnonymousPrincipal.class);
- clearPrincipals(GrantedAuthorityPrincipal.class);
-
- // we resync with Spring Security since the subject may have been reused
- // in beetween
- // TODO: check if this is clean
- // subject.getPrincipals().addAll(syncPrincipals());
-
- return true;
- }
-
- private <T extends Principal> void clearPrincipals(Class<T> clss) {
- Set<T> principals = subject.getPrincipals(clss);
- if (principals != null)
- principals.clear();
- }
-
- @SuppressWarnings("rawtypes")
- @Override
- protected void doInit(CallbackHandler callbackHandler, Session session,
- Map options) throws LoginException {
- }
-
- @Override
- protected boolean impersonate(Principal principal, Credentials credentials)
- throws RepositoryException, LoginException {
- throw new UnsupportedOperationException(
- "Impersonation is not yet supported");
- }
-
- @Override
- protected Authentication getAuthentication(final Principal principal,
- Credentials creds) throws RepositoryException {
- if (principal instanceof Group) {
- return null;
- }
- return new Authentication() {
- public boolean canHandle(Credentials credentials) {
- return principal instanceof org.springframework.security.Authentication;
- }
-
- public boolean authenticate(Credentials credentials)
- throws RepositoryException {
- return ((org.springframework.security.Authentication) principal)
- .isAuthenticated();
- }
- };
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jackrabbit;
-
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.api.security.user.Group;
-import org.apache.jackrabbit.api.security.user.User;
-import org.apache.jackrabbit.api.security.user.UserManager;
-import org.apache.jackrabbit.core.DefaultSecurityManager;
-import org.apache.jackrabbit.core.security.AMContext;
-import org.apache.jackrabbit.core.security.AccessManager;
-import org.apache.jackrabbit.core.security.AnonymousPrincipal;
-import org.apache.jackrabbit.core.security.SecurityConstants;
-import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
-import org.springframework.security.Authentication;
-import org.springframework.security.GrantedAuthority;
-import org.springframework.security.context.SecurityContextHolder;
-
-/** Integrates Spring Security and Jackrabbit Security users and roles. */
-public class ArgeoSecurityManager extends DefaultSecurityManager {
- /** Legacy security sync */
- final static String PROPERTY_JACKRABBIT_SECURITY_SYNC_1_1 = "argeo.jackarabbit.securitySync.1.1";
-
- private final static Log log = LogFactory
- .getLog(ArgeoSecurityManager.class);
-
- private static Boolean synchronize = Boolean.parseBoolean(System
- .getProperty(PROPERTY_JACKRABBIT_SECURITY_SYNC_1_1, "false"));
-
- /** TODO? use a bounded buffer */
- private Map<String, String> userRolesCache = Collections
- .synchronizedMap(new HashMap<String, String>());
-
- @Override
- public AccessManager getAccessManager(Session session, AMContext amContext)
- throws RepositoryException {
- synchronized (getSystemSession()) {
- return super.getAccessManager(session, amContext);
- }
- }
-
- @Override
- public UserManager getUserManager(Session session)
- throws RepositoryException {
- synchronized (getSystemSession()) {
- return super.getUserManager(session);
- }
- }
-
- /**
- * Since this is called once when the session is created, we take the
- * opportunity to make sure that Jackrabbit users and groups reflect Spring
- * Security name and authorities.
- */
- @Override
- public String getUserID(Subject subject, String workspaceName)
- throws RepositoryException {
- if (!synchronize) {
- Authentication authentication = SecurityContextHolder.getContext()
- .getAuthentication();
- if (authentication != null)
- return authentication.getName();
- else
- return super.getUserID(subject, workspaceName);
- }
-
- if (log.isTraceEnabled())
- log.trace(subject);
- // skip anonymous user (no rights)
- if (!subject.getPrincipals(AnonymousPrincipal.class).isEmpty())
- return super.getUserID(subject, workspaceName);
- // skip Jackrabbit system user (all rights)
- if (!subject.getPrincipals(ArgeoSystemPrincipal.class).isEmpty())
- return super.getUserID(subject, workspaceName);
-
- // retrieve Spring authentication from JAAS
- // TODO? use Spring Security context holder
- Authentication authen;
- Set<Authentication> authens = subject
- .getPrincipals(Authentication.class);
- String userId = super.getUserID(subject, workspaceName);
- if (authens.size() == 0) {
- // make sure that logged-in user has a Principal, useful for testing
- // using an admin user
- UserManager systemUm = getSystemUserManager(null);
- if (systemUm.getAuthorizable(userId) == null)
- systemUm.createUser(userId, "");
- } else {// Spring Security
- authen = authens.iterator().next();
-
- if (!userId.equals(authen.getName()))
- log.warn("User ID is '" + userId + "' but authen is "
- + authen.getName());
- StringBuffer roles = new StringBuffer("");
- GrantedAuthority[] authorities = authen.getAuthorities();
- for (GrantedAuthority ga : authorities) {
- roles.append(ga.toString());
- }
-
- // do not sync if not changed
- if (userRolesCache.containsKey(userId)
- && userRolesCache.get(userId).equals(roles.toString()))
- return userId;
-
- // sync Spring and Jackrabbit
- // workspace is irrelevant here
- UserManager systemUm = getSystemUserManager(null);
- syncSpringAndJackrabbitSecurity(systemUm, authen);
- userRolesCache.put(userId, roles.toString());
- }
- return userId;
- }
-
- /**
- * Make sure that the Jackrabbit security model contains this user and its
- * granted authorities
- */
- static private void syncSpringAndJackrabbitSecurity(UserManager systemUm,
- Authentication authen) throws RepositoryException {
- long begin = System.currentTimeMillis();
-
- String userId = authen.getName();
- User user = (User) systemUm.getAuthorizable(userId);
- if (user == null) {
- user = systemUm.createUser(userId, authen.getCredentials()
- .toString(), authen, null);
- log.info(userId + " added as " + user);
- }
-
- // process groups
- List<String> userGroupIds = new ArrayList<String>();
- for (GrantedAuthority ga : authen.getAuthorities()) {
- Group group = (Group) systemUm.getAuthorizable(ga.getAuthority());
- if (group == null) {
- group = systemUm.createGroup(ga.getAuthority());
- log.info(ga.getAuthority() + " added as " + group);
- }
- if (!group.isMember(user))
- group.addMember(user);
- userGroupIds.add(ga.getAuthority());
- }
-
- // check if user has not been removed from some groups
- for (Iterator<Group> it = user.declaredMemberOf(); it.hasNext();) {
- Group group = it.next();
- if (!userGroupIds.contains(group.getID()))
- group.removeMember(user);
- }
-
- if (log.isTraceEnabled())
- log.trace("Spring and Jackrabbit Security synchronized for user "
- + userId + " in " + (System.currentTimeMillis() - begin)
- + " ms");
- }
-
- @Override
- protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() {
- WorkspaceAccessManager wam = super
- .createDefaultWorkspaceAccessManager();
- return new ArgeoWorkspaceAccessManagerImpl(wam);
- }
-
- private class ArgeoWorkspaceAccessManagerImpl implements SecurityConstants,
- WorkspaceAccessManager {
- private final WorkspaceAccessManager wam;
-
- public ArgeoWorkspaceAccessManagerImpl(WorkspaceAccessManager wam) {
- super();
- this.wam = wam;
- }
-
- public void init(Session systemSession) throws RepositoryException {
- wam.init(systemSession);
- }
-
- public void close() throws RepositoryException {
- }
-
- public boolean grants(Set<Principal> principals, String workspaceName)
- throws RepositoryException {
- // TODO: implements finer access to workspaces
- return true;
- }
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jackrabbit;
-
-import java.security.Principal;
-
-/** Principal for non-interactive system actions. */
-class ArgeoSystemPrincipal implements Principal {
- private String name;
-
- public ArgeoSystemPrincipal(String name) {
- super();
- this.name = name;
- }
-
- public String getName() {
- return name;
- }
-
- @Override
- public int hashCode() {
- return getName().hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof ArgeoSystemPrincipal))
- return false;
- return getName().equals(((ArgeoSystemPrincipal) obj).getName());
- }
-
- @Override
- public String toString() {
- return "Argeo System (non interactive) name=" + getName();
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jackrabbit;
-
-import java.security.Principal;
-
-import org.springframework.security.GrantedAuthority;
-
-/** Wraps a {@link GrantedAuthority} as a principal. */
-class GrantedAuthorityPrincipal implements Principal {
- private final GrantedAuthority grantedAuthority;
-
- public GrantedAuthorityPrincipal(GrantedAuthority grantedAuthority) {
- this.grantedAuthority = grantedAuthority;
- }
-
- public String getName() {
- return grantedAuthority.getAuthority();
- }
-
- @Override
- public int hashCode() {
- return getName().hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof GrantedAuthorityPrincipal))
- return false;
- return getName().equals(((GrantedAuthorityPrincipal) obj).getName());
- }
-
- @Override
- public String toString() {
- return "Granted Authority " + getName();
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.security.jackrabbit;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.security.user.Group;
-import org.apache.jackrabbit.api.security.user.User;
-import org.apache.jackrabbit.api.security.user.UserManager;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.security.jcr.SimpleJcrSecurityModel;
-
-/** Make sure that user authorizable exists before syncing user directories. */
-public class JackrabbitSecurityModel extends SimpleJcrSecurityModel {
- private final static Log log = LogFactory
- .getLog(JackrabbitSecurityModel.class);
-
- @Override
- public synchronized Node sync(Session session, String username,
- List<String> roles) {
- if (!(session instanceof JackrabbitSession))
- return super.sync(session, username, roles);
-
- try {
- UserManager userManager = ((JackrabbitSession) session)
- .getUserManager();
- User user = (User) userManager.getAuthorizable(username);
- if (user != null) {
- String principalName = user.getPrincipal().getName();
- if (!principalName.equals(username)) {
- log.warn("Jackrabbit principal is '" + principalName
- + "' but username is '" + username
- + "'. Recreating...");
- user.remove();
- user = userManager.createUser(username, "");
- }
- } else {
- // create new principal
- user = userManager.createUser(username, "");
- log.info(username + " added as Jackrabbit user " + user);
- }
-
- // generic JCR sync
- Node userProfile = super.sync(session, username, roles);
-
- Boolean enabled = userProfile.getProperty(ArgeoNames.ARGEO_ENABLED)
- .getBoolean();
- if (enabled && user.isDisabled())
- user.disable(null);
- else if (!enabled && !user.isDisabled())
- user.disable(userProfile.getPath() + " is disabled");
-
- // Sync Jackrabbit roles
- if (roles != null)
- syncRoles(userManager, user, roles);
-
- return userProfile;
- } catch (RepositoryException e) {
- throw new ArgeoException(
- "Cannot perform Jackrabbit specific operations", e);
- }
- }
-
- /** Make sure Jackrabbit roles are in line with authentication */
- void syncRoles(UserManager userManager, User user, List<String> roles)
- throws RepositoryException {
- List<String> userGroupIds = new ArrayList<String>();
- for (String role : roles) {
- Group group = (Group) userManager.getAuthorizable(role);
- if (group == null) {
- group = userManager.createGroup(role);
- log.info(role + " added as " + group);
- }
- if (!group.isMember(user))
- group.addMember(user);
- userGroupIds.add(role);
- }
-
- // check if user has not been removed from some groups
- for (Iterator<Group> it = user.declaredMemberOf(); it.hasNext();) {
- Group group = it.next();
- if (!userGroupIds.contains(group.getID()))
- group.removeMember(user);
- }
- }
-}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.security.jackrabbit;
+
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.core.id.ItemId;
+import org.apache.jackrabbit.core.security.DefaultAccessManager;
+import org.apache.jackrabbit.spi.Path;
+
+/**
+ * Intermediary class in order to have a consistent naming in config files. Does
+ * nothing for the time being, but may in the future.
+ */
+public class ArgeoAccessManager extends DefaultAccessManager {
+
+ @Override
+ public boolean canRead(Path itemPath, ItemId itemId)
+ throws RepositoryException {
+ return super.canRead(itemPath, itemId);
+ }
+
+ @Override
+ public Privilege[] getPrivileges(String absPath)
+ throws PathNotFoundException, RepositoryException {
+ return super.getPrivileges(absPath);
+ }
+
+ @Override
+ public boolean hasPrivileges(String absPath, Privilege[] privileges)
+ throws PathNotFoundException, RepositoryException {
+ return super.hasPrivileges(absPath, privileges);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.security.jackrabbit;
+
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Credentials;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+
+import org.apache.jackrabbit.core.security.AnonymousPrincipal;
+import org.apache.jackrabbit.core.security.authentication.AbstractLoginModule;
+import org.apache.jackrabbit.core.security.authentication.Authentication;
+import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
+import org.argeo.security.SystemAuthentication;
+import org.springframework.security.GrantedAuthority;
+import org.springframework.security.context.SecurityContextHolder;
+import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
+
+/** Jackrabbit login mechanism based on Spring Security */
+public class ArgeoLoginModule extends AbstractLoginModule {
+ private String adminRole = "ROLE_ADMIN";
+
+ @SuppressWarnings("unused")
+ @Override
+ public boolean login() throws LoginException {
+ boolean loginOk = super.login();
+ if (!loginOk) {
+ org.springframework.security.Authentication authen = (org.springframework.security.Authentication) SecurityContextHolder
+ .getContext().getAuthentication();
+ }
+ return loginOk;
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public boolean commit() throws LoginException {
+ boolean commitOk = super.commit();
+ if (!commitOk) {
+ org.springframework.security.Authentication authen = (org.springframework.security.Authentication) SecurityContextHolder
+ .getContext().getAuthentication();
+ }
+ return commitOk;
+ }
+
+ /**
+ * Returns the Spring {@link org.springframework.security.Authentication}
+ * (which can be null)
+ */
+ @Override
+ protected Principal getPrincipal(Credentials credentials) {
+ org.springframework.security.Authentication authen = SecurityContextHolder
+ .getContext().getAuthentication();
+ return authen;
+ }
+
+ protected Set<Principal> getPrincipals() {
+ // clear already registered Jackrabbit principals
+ // clearPrincipals(AdminPrincipal.class);
+ // clearPrincipals(AnonymousPrincipal.class);
+ // clearPrincipals(GrantedAuthorityPrincipal.class);
+
+ return syncPrincipals();
+ }
+
+ protected Set<Principal> syncPrincipals() {
+ // use linked HashSet instead of HashSet in order to maintain the order
+ // of principals (as in the Subject).
+ org.springframework.security.Authentication authen = (org.springframework.security.Authentication) principal;
+
+ Set<Principal> principals = new LinkedHashSet<Principal>();
+ principals.add(authen);
+
+ if (authen instanceof SystemAuthentication) {
+ principals.add(new AdminPrincipal(authen.getName()));
+ principals.add(new ArgeoSystemPrincipal(authen.getName()));
+ } else if (authen instanceof AnonymousAuthenticationToken) {
+ principals.add(new AnonymousPrincipal());
+ } else {
+ for (GrantedAuthority ga : authen.getAuthorities()) {
+ principals.add(new GrantedAuthorityPrincipal(ga));
+ // FIXME: make it more generic
+ if (adminRole.equals(ga.getAuthority()))
+ principals.add(new AdminPrincipal(authen.getName()));
+ }
+ }
+
+ // remove previous credentials
+ Set<SimpleCredentials> thisCredentials = subject
+ .getPublicCredentials(SimpleCredentials.class);
+ if (thisCredentials != null)
+ thisCredentials.clear();
+ // override credentials since we did not used the one passed to us
+ // credentials = new SimpleCredentials(authen.getName(), authen
+ // .getCredentials().toString().toCharArray());
+
+ return principals;
+ }
+
+ /**
+ * Super implementation removes all {@link Principal}, the Spring
+ * {@link org.springframework.security.Authentication} as well. Here we
+ * simply clear Jackrabbit related {@link Principal}s.
+ */
+ @Override
+ public boolean logout() throws LoginException {
+ clearPrincipals(AdminPrincipal.class);
+ clearPrincipals(ArgeoSystemPrincipal.class);
+ clearPrincipals(AnonymousPrincipal.class);
+ clearPrincipals(GrantedAuthorityPrincipal.class);
+
+ // we resync with Spring Security since the subject may have been reused
+ // in beetween
+ // TODO: check if this is clean
+ // subject.getPrincipals().addAll(syncPrincipals());
+
+ return true;
+ }
+
+ private <T extends Principal> void clearPrincipals(Class<T> clss) {
+ Set<T> principals = subject.getPrincipals(clss);
+ if (principals != null)
+ principals.clear();
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ protected void doInit(CallbackHandler callbackHandler, Session session,
+ Map options) throws LoginException {
+ }
+
+ @Override
+ protected boolean impersonate(Principal principal, Credentials credentials)
+ throws RepositoryException, LoginException {
+ throw new UnsupportedOperationException(
+ "Impersonation is not yet supported");
+ }
+
+ @Override
+ protected Authentication getAuthentication(final Principal principal,
+ Credentials creds) throws RepositoryException {
+ if (principal instanceof Group) {
+ return null;
+ }
+ return new Authentication() {
+ public boolean canHandle(Credentials credentials) {
+ return principal instanceof org.springframework.security.Authentication;
+ }
+
+ public boolean authenticate(Credentials credentials)
+ throws RepositoryException {
+ return ((org.springframework.security.Authentication) principal)
+ .isAuthenticated();
+ }
+ };
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.security.jackrabbit;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.Subject;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.core.DefaultSecurityManager;
+import org.apache.jackrabbit.core.security.AMContext;
+import org.apache.jackrabbit.core.security.AccessManager;
+import org.apache.jackrabbit.core.security.AnonymousPrincipal;
+import org.apache.jackrabbit.core.security.SecurityConstants;
+import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
+import org.springframework.security.Authentication;
+import org.springframework.security.GrantedAuthority;
+import org.springframework.security.context.SecurityContextHolder;
+
+/** Integrates Spring Security and Jackrabbit Security users and roles. */
+public class ArgeoSecurityManager extends DefaultSecurityManager {
+ /** Legacy security sync */
+ final static String PROPERTY_JACKRABBIT_SECURITY_SYNC_1_1 = "argeo.jackarabbit.securitySync.1.1";
+
+ private final static Log log = LogFactory
+ .getLog(ArgeoSecurityManager.class);
+
+ private static Boolean synchronize = Boolean.parseBoolean(System
+ .getProperty(PROPERTY_JACKRABBIT_SECURITY_SYNC_1_1, "false"));
+
+ /** TODO? use a bounded buffer */
+ private Map<String, String> userRolesCache = Collections
+ .synchronizedMap(new HashMap<String, String>());
+
+ @Override
+ public AccessManager getAccessManager(Session session, AMContext amContext)
+ throws RepositoryException {
+ synchronized (getSystemSession()) {
+ return super.getAccessManager(session, amContext);
+ }
+ }
+
+ @Override
+ public UserManager getUserManager(Session session)
+ throws RepositoryException {
+ synchronized (getSystemSession()) {
+ return super.getUserManager(session);
+ }
+ }
+
+ /**
+ * Since this is called once when the session is created, we take the
+ * opportunity to make sure that Jackrabbit users and groups reflect Spring
+ * Security name and authorities.
+ */
+ @Override
+ public String getUserID(Subject subject, String workspaceName)
+ throws RepositoryException {
+ if (!synchronize) {
+ Authentication authentication = SecurityContextHolder.getContext()
+ .getAuthentication();
+ if (authentication != null)
+ return authentication.getName();
+ else
+ return super.getUserID(subject, workspaceName);
+ }
+
+ if (log.isTraceEnabled())
+ log.trace(subject);
+ // skip anonymous user (no rights)
+ if (!subject.getPrincipals(AnonymousPrincipal.class).isEmpty())
+ return super.getUserID(subject, workspaceName);
+ // skip Jackrabbit system user (all rights)
+ if (!subject.getPrincipals(ArgeoSystemPrincipal.class).isEmpty())
+ return super.getUserID(subject, workspaceName);
+
+ // retrieve Spring authentication from JAAS
+ // TODO? use Spring Security context holder
+ Authentication authen;
+ Set<Authentication> authens = subject
+ .getPrincipals(Authentication.class);
+ String userId = super.getUserID(subject, workspaceName);
+ if (authens.size() == 0) {
+ // make sure that logged-in user has a Principal, useful for testing
+ // using an admin user
+ UserManager systemUm = getSystemUserManager(null);
+ if (systemUm.getAuthorizable(userId) == null)
+ systemUm.createUser(userId, "");
+ } else {// Spring Security
+ authen = authens.iterator().next();
+
+ if (!userId.equals(authen.getName()))
+ log.warn("User ID is '" + userId + "' but authen is "
+ + authen.getName());
+ StringBuffer roles = new StringBuffer("");
+ GrantedAuthority[] authorities = authen.getAuthorities();
+ for (GrantedAuthority ga : authorities) {
+ roles.append(ga.toString());
+ }
+
+ // do not sync if not changed
+ if (userRolesCache.containsKey(userId)
+ && userRolesCache.get(userId).equals(roles.toString()))
+ return userId;
+
+ // sync Spring and Jackrabbit
+ // workspace is irrelevant here
+ UserManager systemUm = getSystemUserManager(null);
+ syncSpringAndJackrabbitSecurity(systemUm, authen);
+ userRolesCache.put(userId, roles.toString());
+ }
+ return userId;
+ }
+
+ /**
+ * Make sure that the Jackrabbit security model contains this user and its
+ * granted authorities
+ */
+ static private void syncSpringAndJackrabbitSecurity(UserManager systemUm,
+ Authentication authen) throws RepositoryException {
+ long begin = System.currentTimeMillis();
+
+ String userId = authen.getName();
+ User user = (User) systemUm.getAuthorizable(userId);
+ if (user == null) {
+ user = systemUm.createUser(userId, authen.getCredentials()
+ .toString(), authen, null);
+ log.info(userId + " added as " + user);
+ }
+
+ // process groups
+ List<String> userGroupIds = new ArrayList<String>();
+ for (GrantedAuthority ga : authen.getAuthorities()) {
+ Group group = (Group) systemUm.getAuthorizable(ga.getAuthority());
+ if (group == null) {
+ group = systemUm.createGroup(ga.getAuthority());
+ log.info(ga.getAuthority() + " added as " + group);
+ }
+ if (!group.isMember(user))
+ group.addMember(user);
+ userGroupIds.add(ga.getAuthority());
+ }
+
+ // check if user has not been removed from some groups
+ for (Iterator<Group> it = user.declaredMemberOf(); it.hasNext();) {
+ Group group = it.next();
+ if (!userGroupIds.contains(group.getID()))
+ group.removeMember(user);
+ }
+
+ if (log.isTraceEnabled())
+ log.trace("Spring and Jackrabbit Security synchronized for user "
+ + userId + " in " + (System.currentTimeMillis() - begin)
+ + " ms");
+ }
+
+ @Override
+ protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() {
+ WorkspaceAccessManager wam = super
+ .createDefaultWorkspaceAccessManager();
+ return new ArgeoWorkspaceAccessManagerImpl(wam);
+ }
+
+ private class ArgeoWorkspaceAccessManagerImpl implements SecurityConstants,
+ WorkspaceAccessManager {
+ private final WorkspaceAccessManager wam;
+
+ public ArgeoWorkspaceAccessManagerImpl(WorkspaceAccessManager wam) {
+ super();
+ this.wam = wam;
+ }
+
+ public void init(Session systemSession) throws RepositoryException {
+ wam.init(systemSession);
+ }
+
+ public void close() throws RepositoryException {
+ }
+
+ public boolean grants(Set<Principal> principals, String workspaceName)
+ throws RepositoryException {
+ // TODO: implements finer access to workspaces
+ return true;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.security.jackrabbit;
+
+import java.security.Principal;
+
+/** Principal for non-interactive system actions. */
+class ArgeoSystemPrincipal implements Principal {
+ private String name;
+
+ public ArgeoSystemPrincipal(String name) {
+ super();
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ArgeoSystemPrincipal))
+ return false;
+ return getName().equals(((ArgeoSystemPrincipal) obj).getName());
+ }
+
+ @Override
+ public String toString() {
+ return "Argeo System (non interactive) name=" + getName();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.security.jackrabbit;
+
+import java.security.Principal;
+
+import org.springframework.security.GrantedAuthority;
+
+/** Wraps a {@link GrantedAuthority} as a principal. */
+class GrantedAuthorityPrincipal implements Principal {
+ private final GrantedAuthority grantedAuthority;
+
+ public GrantedAuthorityPrincipal(GrantedAuthority grantedAuthority) {
+ this.grantedAuthority = grantedAuthority;
+ }
+
+ public String getName() {
+ return grantedAuthority.getAuthority();
+ }
+
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof GrantedAuthorityPrincipal))
+ return false;
+ return getName().equals(((GrantedAuthorityPrincipal) obj).getName());
+ }
+
+ @Override
+ public String toString() {
+ return "Granted Authority " + getName();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.security.jackrabbit;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoNames;
+import org.argeo.security.jcr.SimpleJcrSecurityModel;
+
+/** Make sure that user authorizable exists before syncing user directories. */
+public class JackrabbitSecurityModel extends SimpleJcrSecurityModel {
+ private final static Log log = LogFactory
+ .getLog(JackrabbitSecurityModel.class);
+
+ @Override
+ public synchronized Node sync(Session session, String username,
+ List<String> roles) {
+ if (!(session instanceof JackrabbitSession))
+ return super.sync(session, username, roles);
+
+ try {
+ UserManager userManager = ((JackrabbitSession) session)
+ .getUserManager();
+ User user = (User) userManager.getAuthorizable(username);
+ if (user != null) {
+ String principalName = user.getPrincipal().getName();
+ if (!principalName.equals(username)) {
+ log.warn("Jackrabbit principal is '" + principalName
+ + "' but username is '" + username
+ + "'. Recreating...");
+ user.remove();
+ user = userManager.createUser(username, "");
+ }
+ } else {
+ // create new principal
+ user = userManager.createUser(username, "");
+ log.info(username + " added as Jackrabbit user " + user);
+ }
+
+ // generic JCR sync
+ Node userProfile = super.sync(session, username, roles);
+
+ Boolean enabled = userProfile.getProperty(ArgeoNames.ARGEO_ENABLED)
+ .getBoolean();
+ if (enabled && user.isDisabled())
+ user.disable(null);
+ else if (!enabled && !user.isDisabled())
+ user.disable(userProfile.getPath() + " is disabled");
+
+ // Sync Jackrabbit roles
+ if (roles != null)
+ syncRoles(userManager, user, roles);
+
+ return userProfile;
+ } catch (RepositoryException e) {
+ throw new ArgeoException(
+ "Cannot perform Jackrabbit specific operations", e);
+ }
+ }
+
+ /** Make sure Jackrabbit roles are in line with authentication */
+ void syncRoles(UserManager userManager, User user, List<String> roles)
+ throws RepositoryException {
+ List<String> userGroupIds = new ArrayList<String>();
+ for (String role : roles) {
+ Group group = (Group) userManager.getAuthorizable(role);
+ if (group == null) {
+ group = userManager.createGroup(role);
+ log.info(role + " added as " + group);
+ }
+ if (!group.isMember(user))
+ group.addMember(user);
+ userGroupIds.add(role);
+ }
+
+ // check if user has not been removed from some groups
+ for (Iterator<Group> it = user.declaredMemberOf(); it.hasNext();) {
+ Group group = it.next();
+ if (!userGroupIds.contains(group.getID()))
+ group.removeMember(user);
+ }
+ }
+}