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,\
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,
--- /dev/null
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:security="http://www.springframework.org/schema/security"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+ http://www.springframework.org/schema/security
+ http://www.springframework.org/schema/security/spring-security-2.0.4.xsd
+ http://www.springframework.org/schema/util
+ http://www.springframework.org/schema/util/spring-util-2.5.xsd">
+
+
+ <bean id="jcrUserDetailsContextMapper" class="org.argeo.security.ldap.jcr.JcrUserDetailsContextMapper">
+ <property name="systemExecutionService" ref="systemExecutionService" />
+ <property name="propertyToAttributes">
+ <map>
+ <entry value="cn">
+ <key>
+ <util:constant static-field="org.argeo.jcr.ArgeoNames.ARGEO_DISPLAY_NAME" />
+ </key>
+ </entry>
+ <entry value="givenName">
+ <key>
+ <util:constant static-field="org.argeo.jcr.ArgeoNames.ARGEO_FIRST_NAME" />
+ </key>
+ </entry>
+ <entry value="sn">
+ <key>
+ <util:constant static-field="org.argeo.jcr.ArgeoNames.ARGEO_LAST_NAME" />
+ </key>
+ </entry>
+ <entry value="mail">
+ <key>
+ <util:constant static-field="org.argeo.jcr.ArgeoNames.ARGEO_PRIMARY_EMAIL" />
+ </key>
+ </entry>
+ <entry value="o">
+ <key>
+ <util:constant
+ static-field="org.argeo.jcr.ArgeoNames.ARGEO_PRIMARY_ORGANIZATION" />
+ </key>
+ </entry>
+ </map>
+ </property>
+
+ </bean>
+</beans>
<!-- REFERENCES -->\r
<list id="userNatureMappers" interface="org.argeo.security.ldap.UserNatureMapper"\r
cardinality="0..N" />\r
+ <reference id="repositoryFactory" interface="javax.jcr.RepositoryFactory"\r
+ cardinality="0..1">\r
+ <listener ref="jcrUserDetailsContextMapper" bind-method="register"\r
+ unbind-method="unregister" />\r
+ </reference>\r
+ <reference id="systemExecutionService" interface="org.argeo.security.SystemExecutionService" />\r
\r
<!-- SERVICES -->\r
<service ref="authenticationProvider"\r
<constructor-arg>
<bean factory-bean="securityDao" factory-method="getAuthoritiesPopulator" />
</constructor-arg>
- <property name="userDetailsContextMapper">
- <bean factory-bean="securityDao" factory-method="getUserDetailsMapper" />
- </property>
+ <property name="userDetailsContextMapper" ref="jcrUserDetailsContextMapper" />
</bean>
<bean id="securityDao" class="org.argeo.security.ldap.ArgeoSecurityDaoLdap">
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">\r
\r
<!-- REFERENCES -->\r
- <reference id="currentUserDao" interface="org.argeo.security.CurrentUserDao" />\r
+ <reference id="currentUserDao" interface="org.argeo.security.CurrentUserDao"\r
+ cardinality="0..1" />\r
\r
<list id="authenticationProviders"\r
interface="org.springframework.security.providers.AuthenticationProvider"\r
.authenticate(new InternalAuthentication(
systemAuthenticationKey));
securityContext.setAuthentication(auth);
-
- runnable.run();
-
- // remove the authentication
- securityContext.setAuthentication(null);
+ try {
+ runnable.run();
+ } finally {
+ // remove the authentication
+ securityContext.setAuthentication(null);
+ }
}
};
}
package org.argeo.security.jcr;
-import java.util.ArrayList;
import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
import java.util.Map;
import javax.jcr.Node;
/** Connects to a JCR repository and delegate authentication to it. */
public class JcrAuthenticationProvider implements AuthenticationProvider {
- private List<RepositoryFactory> repositoryFactories = new ArrayList<RepositoryFactory>();
+ private RepositoryFactory repositoryFactory;
private final String defaultHome;
private final String userRole;
parameters.put(ArgeoJcrConstants.JCR_REPOSITORY_URI, url);
Repository repository = null;
- for (Iterator<RepositoryFactory> it = repositoryFactories
- .iterator(); it.hasNext();) {
- repository = it.next().getRepository(parameters);
- }
+ repository = repositoryFactory.getRepository(parameters);
if (repository == null)
return null;
}
}
- public void setRepositoryFactories(
- List<RepositoryFactory> repositoryFactories) {
- this.repositoryFactories = repositoryFactories;
+ public void register(RepositoryFactory repositoryFactory,
+ Map<String, String> parameters) {
+ this.repositoryFactory = repositoryFactory;
+ }
+
+ public void unregister(RepositoryFactory repositoryFactory,
+ Map<String, String> parameters) {
+ this.repositoryFactory = null;
}
public String getDefaultHome() {
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;
+ }
}
http://www.springframework.org/schema/util/spring-util-2.5.xsd">\r
\r
<!-- REFERENCE -->\r
- <list id="repositoryFactories" interface="javax.jcr.RepositoryFactory"\r
- cardinality="0..N" />\r
+ <reference id="repositoryFactory" interface="javax.jcr.RepositoryFactory"\r
+ cardinality="0..1">\r
+ <listener ref="jcrAuthenticationProvider" bind-method="register"\r
+ unbind-method="unregister" />\r
+ </reference>\r
<list id="repositories" interface="javax.jcr.Repository"\r
cardinality="0..N">\r
<listener ref="repositoryFactory" bind-method="register"\r
<property name="workspace" value="${argeo.node.repo.workspace}" />
</bean>
- <bean id="jcrAuthenticationProvider" class="org.argeo.security.jackrabbit.providers.JackrabbitAuthenticationProvider">
- <property name="repositoryFactories" ref="repositoryFactories" />
- </bean>
+ <bean id="jcrAuthenticationProvider"
+ class="org.argeo.security.jackrabbit.providers.JackrabbitAuthenticationProvider" />
<bean id="repositoryFactory" class="org.argeo.jackrabbit.JackrabbitRepositoryFactory">
</bean>
/** 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";
+
}
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";
+
}
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;
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;
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);
/**
}
/**
- * 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) {
currentNode = (Node) session.getItem(current.toString());
}
}
- session.save();
+ // session.save();
return currentNode;
} catch (RepositoryException e) {
throw new ArgeoException("Cannot mkdirs " + path, e);
}
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<String, String> parameters = new HashMap<String, String>();
+ 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<String, String> parameters = new HashMap<String, String>();
+ 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);
+ }
+ }
}