From: Mathieu Baudier Date: Fri, 18 Mar 2011 11:03:39 +0000 (+0000) Subject: Integrate LDAP and JCR X-Git-Tag: argeo-commons-2.1.30~1344 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=802beab5459c8da4970215886babb45d968e4639;p=lgpl%2Fargeo-commons.git Integrate LDAP and JCR git-svn-id: https://svn.argeo.org/commons/trunk@4320 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- diff --git a/demo/argeo-node-web.properties b/demo/argeo-node-web.properties index 726f225c3..7cbe55435 100644 --- a/demo/argeo-node-web.properties +++ b/demo/argeo-node-web.properties @@ -5,7 +5,6 @@ org.argeo.node.repo.jackrabbit,\ org.argeo.security.dao.ldap,\ org.argeo.security.services,\ org.argeo.security.services.admin,\ -com.springsource.javax.servlet,\ org.argeo.security.equinox,\ org.eclipse.core.runtime,\ org.eclipse.equinox.common,\ diff --git a/security/modules/org.argeo.security.dao.ldap/META-INF/MANIFEST.MF b/security/modules/org.argeo.security.dao.ldap/META-INF/MANIFEST.MF index 85bbf4b21..d377808d8 100644 --- a/security/modules/org.argeo.security.dao.ldap/META-INF/MANIFEST.MF +++ b/security/modules/org.argeo.security.dao.ldap/META-INF/MANIFEST.MF @@ -1,8 +1,11 @@ Bundle-SymbolicName: org.argeo.security.dao.ldap Bundle-Version: 0.2.3.SNAPSHOT Import-Package: com.sun.jndi.ldap;resolution:=optional, + javax.jcr;version="[2.0.0,3.0.0)", + org.argeo.jcr, org.argeo.security, org.argeo.security.ldap, + org.argeo.security.ldap.jcr, org.argeo.security.ldap.nature, org.argeo.security.nature, org.springframework.beans.factory.config, diff --git a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/jcr.xml b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/jcr.xml new file mode 100644 index 000000000..2a0a08c98 --- /dev/null +++ b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/jcr.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-osgi.xml b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-osgi.xml index 3ae43c330..fcfa5fed1 100644 --- a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-osgi.xml +++ b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-osgi.xml @@ -9,6 +9,12 @@ + + + + - - - + diff --git a/security/modules/org.argeo.security.services/META-INF/spring/osgi.xml b/security/modules/org.argeo.security.services/META-INF/spring/osgi.xml index 2ee9bf3f5..f83086a2b 100644 --- a/security/modules/org.argeo.security.services/META-INF/spring/osgi.xml +++ b/security/modules/org.argeo.security.services/META-INF/spring/osgi.xml @@ -7,7 +7,8 @@ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - + repositoryFactories = new ArrayList(); + private RepositoryFactory repositoryFactory; private final String defaultHome; private final String userRole; @@ -58,10 +55,7 @@ public class JcrAuthenticationProvider implements AuthenticationProvider { parameters.put(ArgeoJcrConstants.JCR_REPOSITORY_URI, url); Repository repository = null; - for (Iterator it = repositoryFactories - .iterator(); it.hasNext();) { - repository = it.next().getRepository(parameters); - } + repository = repositoryFactory.getRepository(parameters); if (repository == null) return null; @@ -120,9 +114,14 @@ public class JcrAuthenticationProvider implements AuthenticationProvider { } } - public void setRepositoryFactories( - List repositoryFactories) { - this.repositoryFactories = repositoryFactories; + public void register(RepositoryFactory repositoryFactory, + Map parameters) { + this.repositoryFactory = repositoryFactory; + } + + public void unregister(RepositoryFactory repositoryFactory, + Map parameters) { + this.repositoryFactory = null; } public String getDefaultHome() { diff --git a/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java b/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java index b5b77474c..86c788a44 100644 --- a/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java +++ b/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java @@ -1,25 +1,184 @@ package org.argeo.security.ldap.jcr; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; import javax.jcr.Session; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.JcrUtils; +import org.argeo.security.SystemExecutionService; +import org.argeo.security.jcr.JcrUserDetails; import org.springframework.ldap.core.DirContextAdapter; import org.springframework.ldap.core.DirContextOperations; import org.springframework.security.GrantedAuthority; import org.springframework.security.userdetails.UserDetails; import org.springframework.security.userdetails.ldap.UserDetailsContextMapper; +/** + * Maps LDAP attributes and JCR properties. This class is meant to be robust, + * checks of which values should be mandatory should be performed at a higher + * level. + */ public class JcrUserDetailsContextMapper implements UserDetailsContextMapper { - private Session session; + private final static Log log = LogFactory + .getLog(JcrUserDetailsContextMapper.class); + + private Map propertyToAttributes = new HashMap(); + private SystemExecutionService systemExecutionService; + private String homeBasePath = "/home"; + private RepositoryFactory repositoryFactory; + + public UserDetails mapUserFromContext(final DirContextOperations ctx, + final String username, GrantedAuthority[] authorities) { + if (repositoryFactory == null) + throw new ArgeoException("No JCR repository factory registered"); + final String userHomePath = usernameToHomePath(username); + systemExecutionService.executeAsSystem(new Runnable() { + public void run() { + Session session = null; + try { + Repository nodeRepo = JcrUtils.getRepositoryByAlias( + repositoryFactory, ArgeoJcrConstants.ALIAS_NODE); + session = nodeRepo.login(); + Node userProfile = JcrUtils.mkdirs(session, userHomePath + + '/' + ArgeoNames.ARGEO_USER_PROFILE); + for (String jcrProperty : propertyToAttributes.keySet()) + ldapToJcr(userProfile, jcrProperty, ctx); + session.save(); + if (log.isDebugEnabled()) + log.debug("Mapped " + ctx.getDn() + " to " + + userProfile); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot synchronize JCR and LDAP", + e); + } finally { + session.logout(); + } + } + }); - public UserDetails mapUserFromContext(DirContextOperations ctx, - String username, GrantedAuthority[] authority) { - // TODO Auto-generated method stub - return null; + // password + byte[] arr = (byte[]) ctx.getAttributeSortedStringSet("userPassword") + .first(); + JcrUserDetails userDetails = new JcrUserDetails(userHomePath, username, + new String(arr), true, true, true, true, authorities); + Arrays.fill(arr, (byte) 0); + return userDetails; } - public void mapUserToContext(UserDetails user, DirContextAdapter ctx) { - // TODO Auto-generated method stub + public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) { + if (!(user instanceof JcrUserDetails)) + throw new ArgeoException("Unsupported user details: " + + user.getClass()); + + ctx.setAttributeValues("objectClass", new String[] { "inetOrgPerson" }); + ctx.setAttributeValue("uid", user.getUsername()); + ctx.setAttributeValue("userPassword", user.getPassword()); + + final JcrUserDetails jcrUserDetails = (JcrUserDetails) user; + systemExecutionService.executeAsSystem(new Runnable() { + public void run() { + Session session = null; + try { + Repository nodeRepo = JcrUtils.getRepositoryByAlias( + repositoryFactory, ArgeoJcrConstants.ALIAS_NODE); + session = nodeRepo.login(); + Node userProfile = session.getNode(jcrUserDetails + .getHomePath() + + '/' + + ArgeoNames.ARGEO_USER_PROFILE); + for (String jcrProperty : propertyToAttributes.keySet()) + jcrToLdap(userProfile, jcrProperty, ctx); + if (log.isDebugEnabled()) + log.debug("Mapped " + userProfile + " to " + + ctx.getDn()); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot synchronize JCR and LDAP", + e); + } finally { + session.logout(); + } + } + }); + } + protected String usernameToHomePath(String username) { + return homeBasePath + '/' + JcrUtils.firstCharsToPath(username, 2) + + '/' + username; } + protected void ldapToJcr(Node userProfile, String jcrProperty, + DirContextOperations ctx) { + try { + String ldapAttribute; + if (propertyToAttributes.containsKey(jcrProperty)) + ldapAttribute = propertyToAttributes.get(jcrProperty); + else + throw new ArgeoException( + "No LDAP attribute mapped for JCR proprty " + + jcrProperty); + + String value = ctx.getStringAttribute(ldapAttribute); + if (value == null) + return; + userProfile.setProperty(jcrProperty, value); + } catch (Exception e) { + throw new ArgeoException("Cannot map JCR property " + jcrProperty + + " from LDAP", e); + } + } + + protected void jcrToLdap(Node userProfile, String jcrProperty, + DirContextOperations ctx) { + try { + if (!userProfile.hasProperty(jcrProperty)) + return; + String value = userProfile.getProperty(jcrProperty).getString(); + + String ldapAttribute; + if (propertyToAttributes.containsKey(jcrProperty)) + ldapAttribute = propertyToAttributes.get(jcrProperty); + else + throw new ArgeoException( + "No LDAP attribute mapped for JCR proprty " + + jcrProperty); + ctx.setAttributeValue(ldapAttribute, value); + } catch (Exception e) { + throw new ArgeoException("Cannot map JCR property " + jcrProperty + + " from LDAP", e); + } + } + + public void setPropertyToAttributes(Map propertyToAttributes) { + this.propertyToAttributes = propertyToAttributes; + } + + public void setSystemExecutionService( + SystemExecutionService systemExecutionService) { + this.systemExecutionService = systemExecutionService; + } + + public void setHomeBasePath(String homeBasePath) { + this.homeBasePath = homeBasePath; + } + + public void register(RepositoryFactory repositoryFactory, + Map parameters) { + this.repositoryFactory = repositoryFactory; + } + + public void unregister(RepositoryFactory repositoryFactory, + Map parameters) { + this.repositoryFactory = null; + } } diff --git a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml index 9721feebb..f13839713 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml +++ b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml @@ -10,8 +10,11 @@ http://www.springframework.org/schema/util/spring-util-2.5.xsd"> - + + + - - - + diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrConstants.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrConstants.java index 458246ef4..5d5983c40 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrConstants.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrConstants.java @@ -3,7 +3,11 @@ package org.argeo.jcr; /** JCR related constants */ public interface ArgeoJcrConstants { + // parameters (typically for call to a RepositoryFactory) public final static String JCR_REPOSITORY_ALIAS = "argeo.jcr.repository.alias"; public final static String JCR_REPOSITORY_URI = "argeo.jcr.repository.uri"; + // standard aliases + public final static String ALIAS_NODE = "node"; + } diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoNames.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoNames.java index fd2152798..4b5ffb8db 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoNames.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoNames.java @@ -6,4 +6,12 @@ public interface ArgeoNames { public final static String ARGEO_URI = "argeo:uri"; public final static String ARGEO_USER_ID = "argeo:userID"; + + public final static String ARGEO_USER_PROFILE = "argeo:userProfile"; + public final static String ARGEO_DISPLAY_NAME = "argeo:displayName"; + public final static String ARGEO_FIRST_NAME = "argeo:firstName"; + public final static String ARGEO_LAST_NAME = "argeo:lastName"; + public final static String ARGEO_PRIMARY_EMAIL = "argeo:primaryEmail"; + public final static String ARGEO_PRIMARY_ORGANIZATION = "argeo:primaryOrganization"; + } diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java index 8c6d6a78d..ad6ca0edd 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java @@ -23,6 +23,7 @@ import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -35,7 +36,9 @@ import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; +import javax.jcr.Repository; import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.nodetype.NodeType; @@ -47,7 +50,7 @@ import org.apache.commons.logging.LogFactory; import org.argeo.ArgeoException; /** Utility methods to simplify common JCR operations. */ -public class JcrUtils { +public class JcrUtils implements ArgeoJcrConstants { private final static Log log = LogFactory.getLog(JcrUtils.class); /** @@ -219,7 +222,8 @@ public class JcrUtils { } /** - * Creates the nodes making path, if they don't exist. + * Creates the nodes making path, if they don't exist. This is up to the + * caller to save the session. */ public static Node mkdirs(Session session, String path, String type, String intermediaryNodeType, Boolean versioning) { @@ -262,7 +266,7 @@ public class JcrUtils { currentNode = (Node) session.getItem(current.toString()); } } - session.save(); + // session.save(); return currentNode; } catch (RepositoryException e) { throw new ArgeoException("Cannot mkdirs " + path, e); @@ -573,4 +577,40 @@ public class JcrUtils { } return path.toString(); } + + /** + * Wraps the call to the repository factory based on parameter + * {@link ArgeoJcrConstants#JCR_REPOSITORY_ALIAS} in order to simplify it + * and protect against future API changes. + */ + public static Repository getRepositoryByAlias( + RepositoryFactory repositoryFactory, String alias) { + try { + Map parameters = new HashMap(); + parameters.put(JCR_REPOSITORY_ALIAS, alias); + return repositoryFactory.getRepository(parameters); + } catch (RepositoryException e) { + throw new ArgeoException( + "Unexpected exception when trying to retrieve repository with alias " + + alias, e); + } + } + + /** + * Wraps the call to the repository factory based on parameter + * {@link ArgeoJcrConstants#JCR_REPOSITORY_URI} in order to simplify it and + * protect against future API changes. + */ + public static Repository getRepositoryByUri( + RepositoryFactory repositoryFactory, String uri) { + try { + Map parameters = new HashMap(); + parameters.put(JCR_REPOSITORY_URI, uri); + return repositoryFactory.getRepository(parameters); + } catch (RepositoryException e) { + throw new ArgeoException( + "Unexpected exception when trying to retrieve repository with uri " + + uri, e); + } + } }