X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=security%2Fruntime%2Forg.argeo.security.ldap%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fsecurity%2Fldap%2Fjcr%2FJcrUserDetailsContextMapper.java;h=ec4255af9a6a1777583f058fda55cae186e2e902;hb=4c8c237990cda2b1a9be35532796510d9d5734c5;hp=86c788a448e5188f8afd6902188e955b492bf2fb;hpb=802beab5459c8da4970215886babb45d968e4639;p=lgpl%2Fargeo-commons.git 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 86c788a44..ec4255af9 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,26 +1,31 @@ package org.argeo.security.ldap.jcr; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Random; +import java.util.SortedSet; +import java.util.concurrent.Executor; import javax.jcr.Node; -import javax.jcr.Repository; +import javax.jcr.Property; import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; 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.context.SecurityContextHolder; +import org.springframework.security.providers.encoding.PasswordEncoder; import org.springframework.security.userdetails.UserDetails; import org.springframework.security.userdetails.ldap.UserDetailsContextMapper; @@ -29,92 +34,151 @@ import org.springframework.security.userdetails.ldap.UserDetailsContextMapper; * checks of which values should be mandatory should be performed at a higher * level. */ -public class JcrUserDetailsContextMapper implements UserDetailsContextMapper { +public class JcrUserDetailsContextMapper implements UserDetailsContextMapper, + ArgeoNames { private final static Log log = LogFactory .getLog(JcrUserDetailsContextMapper.class); + private String usernameAttribute; + private String passwordAttribute; + private String homeBasePath; + private String[] userClasses; + private Map propertyToAttributes = new HashMap(); - private SystemExecutionService systemExecutionService; - private String homeBasePath = "/home"; - private RepositoryFactory repositoryFactory; + private Executor systemExecutor; + private Session session; + + private PasswordEncoder passwordEncoder; + private final Random random; + + public JcrUserDetailsContextMapper() { + random = createRandom(); + } + + private static Random createRandom() { + try { + return SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + return new Random(System.currentTimeMillis()); + } + } 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() { + if (ctx == null) + throw new ArgeoException("No LDAP information found for user " + + username); + + final StringBuffer userHomePathT = new StringBuffer(""); + Runnable action = 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(); - } + String userHomepath = mapLdapToJcr(username, ctx); + userHomePathT.append(userHomepath); } - }); + }; + + if (SecurityContextHolder.getContext().getAuthentication() == null) { + // authentication + try { + systemExecutor.execute(action); + } finally { + JcrUtils.logoutQuietly(session); + } + } else { + // authenticated user + action.run(); + } // 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); + SortedSet passwordAttributes = ctx + .getAttributeSortedStringSet(passwordAttribute); + String password; + if (passwordAttributes == null || passwordAttributes.size() == 0) { + throw new ArgeoException("No password found for user " + username); + } else { + byte[] arr = (byte[]) passwordAttributes.first(); + password = new String(arr); + // erase password + Arrays.fill(arr, (byte) 0); + } + JcrUserDetails userDetails = new JcrUserDetails( + userHomePathT.toString(), username, password, true, true, true, + true, authorities); return userDetails; } + /** @return path to the user home node */ + protected String mapLdapToJcr(String username, DirContextOperations ctx) { + try { + Node userHome = JcrUtils.getUserHome(session, username); + if (userHome == null) + userHome = JcrUtils.createUserHome(session, homeBasePath, + username); + String userHomePath = userHome.getPath(); + Node userProfile = userHome.getNode(ARGEO_PROFILE); + if (userHome.hasNode(ARGEO_PROFILE)) { + userProfile = userHome.getNode(ARGEO_PROFILE); + } else { + userProfile = userHome.addNode(ARGEO_PROFILE); + userProfile.addMixin(NodeType.MIX_TITLE); + userProfile.addMixin(NodeType.MIX_CREATED); + userProfile.addMixin(NodeType.MIX_LAST_MODIFIED); + } + + for (String jcrProperty : propertyToAttributes.keySet()) + ldapToJcr(userProfile, jcrProperty, ctx); + + // assign default values + if (!userProfile.hasProperty(Property.JCR_DESCRIPTION)) + userProfile.setProperty(Property.JCR_DESCRIPTION, ""); + if (!userProfile.hasProperty(Property.JCR_TITLE)) + userProfile.setProperty(Property.JCR_TITLE, userProfile + .getProperty(ARGEO_FIRST_NAME).getString() + + " " + + userProfile.getProperty(ARGEO_LAST_NAME).getString()); + + session.save(); + if (log.isTraceEnabled()) + log.trace("Mapped " + ctx.getDn() + " to " + userProfile); + return userHomePath; + } catch (Exception e) { + JcrUtils.discardQuietly(session); + throw new ArgeoException("Cannot synchronize JCR and LDAP", e); + } + } + 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()); + ctx.setAttributeValues("objectClass", userClasses); + ctx.setAttributeValue(usernameAttribute, user.getUsername()); + ctx.setAttributeValue(passwordAttribute, + encodePassword(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(); - } - } - }); + try { + Node userProfile = session.getNode(jcrUserDetails.getHomePath() + + '/' + ARGEO_PROFILE); + for (String jcrProperty : propertyToAttributes.keySet()) + jcrToLdap(userProfile, jcrProperty, ctx); + + if (log.isTraceEnabled()) + log.trace("Mapped " + userProfile + " to " + ctx.getDn()); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot synchronize JCR and LDAP", e); + } } - protected String usernameToHomePath(String username) { - return homeBasePath + '/' + JcrUtils.firstCharsToPath(username, 2) - + '/' + username; + protected String encodePassword(String password) { + if (!password.startsWith("{")) { + byte[] salt = new byte[16]; + random.nextBytes(salt); + return passwordEncoder.encodePassword(password, salt); + } else { + return password; + } } protected void ldapToJcr(Node userProfile, String jcrProperty, @@ -141,10 +205,6 @@ public class JcrUserDetailsContextMapper implements UserDetailsContextMapper { 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); @@ -152,6 +212,24 @@ public class JcrUserDetailsContextMapper implements UserDetailsContextMapper { throw new ArgeoException( "No LDAP attribute mapped for JCR proprty " + jcrProperty); + + // fix issue with empty 'sn' in LDAP + if (ldapAttribute.equals("sn") + && (!userProfile.hasProperty(jcrProperty) || userProfile + .getProperty(jcrProperty).getString().trim() + .equals(""))) + userProfile.setProperty(jcrProperty, "empty"); + + if (ldapAttribute.equals("description")) { + String value = userProfile.getProperty(jcrProperty).getString(); + if (value.trim().equals("")) + return; + } + + if (!userProfile.hasProperty(jcrProperty)) + return; + String value = userProfile.getProperty(jcrProperty).getString(); + ctx.setAttributeValue(ldapAttribute, value); } catch (Exception e) { throw new ArgeoException("Cannot map JCR property " + jcrProperty @@ -163,22 +241,32 @@ public class JcrUserDetailsContextMapper implements UserDetailsContextMapper { this.propertyToAttributes = propertyToAttributes; } - public void setSystemExecutionService( - SystemExecutionService systemExecutionService) { - this.systemExecutionService = systemExecutionService; + public void setSystemExecutor(Executor systemExecutor) { + this.systemExecutor = systemExecutor; } public void setHomeBasePath(String homeBasePath) { this.homeBasePath = homeBasePath; } - public void register(RepositoryFactory repositoryFactory, - Map parameters) { - this.repositoryFactory = repositoryFactory; + public void setUsernameAttribute(String usernameAttribute) { + this.usernameAttribute = usernameAttribute; + } + + public void setPasswordAttribute(String passwordAttribute) { + this.passwordAttribute = passwordAttribute; } - public void unregister(RepositoryFactory repositoryFactory, - Map parameters) { - this.repositoryFactory = null; + public void setUserClasses(String[] userClasses) { + this.userClasses = userClasses; } + + public void setPasswordEncoder(PasswordEncoder passwordEncoder) { + this.passwordEncoder = passwordEncoder; + } + + public void setSession(Session session) { + this.session = session; + } + }