From: Mathieu Baudier Date: Tue, 25 Nov 2014 13:16:18 +0000 (+0000) Subject: New project conventions X-Git-Tag: argeo-commons-2.1.30~517 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=00154c058c3ce91af6e264e6f5842e22724ea709;p=lgpl%2Fargeo-commons.git New project conventions git-svn-id: https://svn.argeo.org/commons/trunk@7531 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- diff --git a/org.argeo.security.core/build.properties b/org.argeo.security.core/build.properties index c988eeb5d..a25d2eaec 100644 --- a/org.argeo.security.core/build.properties +++ b/org.argeo.security.core/build.properties @@ -9,4 +9,3 @@ additional.bundles = org.springframework.transaction,\ slf4j.org.apache.commons.logging source.. = src/,\ ext/test/ -bin.. = bin/ diff --git a/org.argeo.security.jackrabbit/.classpath b/org.argeo.security.jackrabbit/.classpath index 5641c7ca3..d2953a684 100644 --- a/org.argeo.security.jackrabbit/.classpath +++ b/org.argeo.security.jackrabbit/.classpath @@ -1,7 +1,9 @@ - - >> - - + + + + diff --git a/org.argeo.security.jackrabbit/bnd.bnd b/org.argeo.security.jackrabbit/bnd.bnd new file mode 100644 index 000000000..6cd12f83d --- /dev/null +++ b/org.argeo.security.jackrabbit/bnd.bnd @@ -0,0 +1,4 @@ +Fragment-Host: org.apache.jackrabbit +Import-Package: org.springframework.core,\ +org.argeo.jcr,\ +* diff --git a/org.argeo.security.jackrabbit/build.properties b/org.argeo.security.jackrabbit/build.properties index 5fc538bc8..30f715358 100644 --- a/org.argeo.security.jackrabbit/build.properties +++ b/org.argeo.security.jackrabbit/build.properties @@ -1,4 +1 @@ -source.. = src/main/java/ -output.. = target/classes/ -bin.includes = META-INF/,\ - . +source.. = src/ diff --git a/org.argeo.security.jackrabbit/pom.xml b/org.argeo.security.jackrabbit/pom.xml index a18c453dd..d615194b8 100644 --- a/org.argeo.security.jackrabbit/pom.xml +++ b/org.argeo.security.jackrabbit/pom.xml @@ -10,37 +10,6 @@ org.argeo.security.jackrabbit Commons Security Jackrabbit - - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.apache.maven.plugins - maven-source-plugin - - - org.apache.maven.plugins - maven-jar-plugin - - - org.apache.felix - maven-bundle-plugin - - - - org.apache.jackrabbit - org.argeo.security.jackrabbit.* - - org.springframework.core, - org.argeo.jcr, - * - - - - - org.argeo.commons diff --git a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoAccessManager.java b/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoAccessManager.java deleted file mode 100644 index 52ea3c984..000000000 --- a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoAccessManager.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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); - } - -} diff --git a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java b/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java deleted file mode 100644 index 43c544083..000000000 --- a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * 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 getPrincipals() { - // clear already registered Jackrabbit principals - // clearPrincipals(AdminPrincipal.class); - // clearPrincipals(AnonymousPrincipal.class); - // clearPrincipals(GrantedAuthorityPrincipal.class); - - return syncPrincipals(); - } - - protected Set 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 principals = new LinkedHashSet(); - 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 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 void clearPrincipals(Class clss) { - Set 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(); - } - }; - } - -} diff --git a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java b/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java deleted file mode 100644 index 3450c75d8..000000000 --- a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * 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 userRolesCache = Collections - .synchronizedMap(new HashMap()); - - @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 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 userGroupIds = new ArrayList(); - 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 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 principals, String workspaceName) - throws RepositoryException { - // TODO: implements finer access to workspaces - return true; - } - } - -} diff --git a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSystemPrincipal.java b/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSystemPrincipal.java deleted file mode 100644 index e38981ef4..000000000 --- a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSystemPrincipal.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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(); - } - -} diff --git a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java b/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java deleted file mode 100644 index 482214e50..000000000 --- a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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(); - } - -} diff --git a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java b/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java deleted file mode 100644 index a9985f94f..000000000 --- a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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 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 roles) - throws RepositoryException { - List userGroupIds = new ArrayList(); - 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 it = user.declaredMemberOf(); it.hasNext();) { - Group group = it.next(); - if (!userGroupIds.contains(group.getID())) - group.removeMember(user); - } - } -} diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java new file mode 100644 index 000000000..52ea3c984 --- /dev/null +++ b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java @@ -0,0 +1,50 @@ +/* + * 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); + } + +} diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoLoginModule.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoLoginModule.java new file mode 100644 index 000000000..43c544083 --- /dev/null +++ b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoLoginModule.java @@ -0,0 +1,178 @@ +/* + * 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 getPrincipals() { + // clear already registered Jackrabbit principals + // clearPrincipals(AdminPrincipal.class); + // clearPrincipals(AnonymousPrincipal.class); + // clearPrincipals(GrantedAuthorityPrincipal.class); + + return syncPrincipals(); + } + + protected Set 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 principals = new LinkedHashSet(); + 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 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 void clearPrincipals(Class clss) { + Set 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(); + } + }; + } + +} diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java new file mode 100644 index 000000000..3450c75d8 --- /dev/null +++ b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java @@ -0,0 +1,213 @@ +/* + * 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 userRolesCache = Collections + .synchronizedMap(new HashMap()); + + @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 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 userGroupIds = new ArrayList(); + 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 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 principals, String workspaceName) + throws RepositoryException { + // TODO: implements finer access to workspaces + return true; + } + } + +} diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoSystemPrincipal.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoSystemPrincipal.java new file mode 100644 index 000000000..e38981ef4 --- /dev/null +++ b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoSystemPrincipal.java @@ -0,0 +1,50 @@ +/* + * 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(); + } + +} diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java new file mode 100644 index 000000000..482214e50 --- /dev/null +++ b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java @@ -0,0 +1,51 @@ +/* + * 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(); + } + +} diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java new file mode 100644 index 000000000..a9985f94f --- /dev/null +++ b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java @@ -0,0 +1,109 @@ +/* + * 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 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 roles) + throws RepositoryException { + List userGroupIds = new ArrayList(); + 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 it = user.declaredMemberOf(); it.hasNext();) { + Group group = it.next(); + if (!userGroupIds.contains(group.getID())) + group.removeMember(user); + } + } +}