Integrate LDAP and JCR
[lgpl/argeo-commons.git] / security / runtime / org.argeo.security.ldap / src / main / java / org / argeo / security / ldap / jcr / JcrUserDetailsContextMapper.java
index b5b77474cb2e40bc71c3a29d797640cc03e72f7e..86c788a448e5188f8afd6902188e955b492bf2fb 100644 (file)
 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<String, String> propertyToAttributes = new HashMap<String, String>();
+       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<String, String> 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<String, String> parameters) {
+               this.repositoryFactory = repositoryFactory;
+       }
+
+       public void unregister(RepositoryFactory repositoryFactory,
+                       Map<String, String> parameters) {
+               this.repositoryFactory = null;
+       }
 }