<?xml version="1.0" encoding="UTF-8"?>
<classpath>
- <classpathentry kind="src" path="src/main/java"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>>>
- <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
- <classpathentry kind="output" path="target/classes"/>
+ <classpathentry kind="src" path="src" />
+ <classpathentry kind="con"
+ path="org.eclipse.pde.core.requiredPlugins" />
+ <classpathentry kind="con"
+ path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" />
+ <classpathentry kind="output" path="bin" />
</classpath>
--- /dev/null
+Import-Package: org.springframework.core,\
+org.springframework.dao,\
+javax.jcr.nodetype,\
+*
\ No newline at end of file
-source.. = src/main/java/
-output.. = target/classes/
-bin.includes = META-INF/,\
- .
+source.. = src/
+++ /dev/null
-/*
- * 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.ldap;
-
-import org.springframework.security.providers.ldap.authenticator.LdapShaPasswordEncoder;
-
-/**
- * {@link LdapShaPasswordEncoder} allowing to configure the usage of salt (APache
- * Directory Server 1.0 does not support bind with SSHA)
- */
-public class ArgeoLdapShaPasswordEncoder extends LdapShaPasswordEncoder {
- private Boolean useSalt = true;
-
- @Override
- public String encodePassword(String rawPass, Object salt) {
- return super.encodePassword(rawPass, useSalt ? salt : null);
- }
-
- public void setUseSalt(Boolean useSalt) {
- this.useSalt = useSalt;
- }
-
-}
+++ /dev/null
-/*
- * 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.ldap;
-
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.argeo.ArgeoException;
-import org.argeo.security.UserAdminService;
-import org.springframework.ldap.core.ContextSource;
-import org.springframework.security.Authentication;
-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.LdapUserDetailsManager;
-
-/** Extends {@link LdapUserDetailsManager} by adding password encoding support. */
-public class ArgeoLdapUserDetailsManager extends LdapUserDetailsManager
- implements UserAdminService {
- private String superUsername = "root";
- private ArgeoUserAdminDaoLdap userAdminDao;
- private PasswordEncoder passwordEncoder;
- private final Random random;
-
- public ArgeoLdapUserDetailsManager(ContextSource contextSource) {
- super(contextSource);
- this.random = createRandom();
- }
-
- private static Random createRandom() {
- try {
- return SecureRandom.getInstance("SHA1PRNG");
- } catch (NoSuchAlgorithmException e) {
- return new Random(System.currentTimeMillis());
- }
- }
-
- @Override
- public void changePassword(String oldPassword, String newPassword) {
- Authentication authentication = SecurityContextHolder.getContext()
- .getAuthentication();
- if (authentication == null)
- throw new ArgeoException(
- "Cannot change password without authentication");
- String username = authentication.getName();
- UserDetails userDetails = loadUserByUsername(username);
- String currentPassword = userDetails.getPassword();
- if (currentPassword == null)
- throw new ArgeoException("Cannot access current password");
- if (!passwordEncoder
- .isPasswordValid(currentPassword, oldPassword, null))
- throw new ArgeoException("Old password invalid");
- // Spring Security LDAP 2.0 is buggy when used with OpenLDAP and called
- // with oldPassword argument
- super.changePassword(null, encodePassword(newPassword));
- }
-
- public void newRole(String role) {
- userAdminDao.createRole(role, superUsername);
- }
-
- public void synchronize() {
- for (String username : userAdminDao.listUsers())
- loadUserByUsername(username);
- // TODO: find a way to remove from JCR
- }
-
- public void deleteRole(String role) {
- userAdminDao.deleteRole(role);
- }
-
- public Set<String> listUsers() {
- return userAdminDao.listUsers();
- }
-
- public Set<String> listUsersInRole(String role) {
- Set<String> lst = new TreeSet<String>(
- userAdminDao.listUsersInRole(role));
- Iterator<String> it = lst.iterator();
- while (it.hasNext()) {
- if (it.next().equals(superUsername)) {
- it.remove();
- break;
- }
- }
- return lst;
- }
-
- public List<String> listUserRoles(String username) {
- UserDetails userDetails = loadUserByUsername(username);
- List<String> roles = new ArrayList<String>();
- for (GrantedAuthority ga : userDetails.getAuthorities()) {
- roles.add(ga.getAuthority());
- }
- return Collections.unmodifiableList(roles);
- }
-
- public Set<String> listEditableRoles() {
- return userAdminDao.listEditableRoles();
- }
-
- protected String encodePassword(String password) {
- if (!password.startsWith("{")) {
- byte[] salt = new byte[16];
- random.nextBytes(salt);
- return passwordEncoder.encodePassword(password, salt);
- } else {
- return password;
- }
- }
-
- public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
- this.passwordEncoder = passwordEncoder;
- }
-
- public void setSuperUsername(String superUsername) {
- this.superUsername = superUsername;
- }
-
- public void setUserAdminDao(ArgeoUserAdminDaoLdap userAdminDao) {
- this.userAdminDao = userAdminDao;
- }
-
-}
+++ /dev/null
-/*
- * 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.ldap;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
-
-import javax.naming.Name;
-import javax.naming.NamingException;
-import javax.naming.directory.DirContext;
-
-import org.springframework.ldap.core.ContextExecutor;
-import org.springframework.ldap.core.ContextMapper;
-import org.springframework.ldap.core.DirContextAdapter;
-import org.springframework.ldap.core.DistinguishedName;
-import org.springframework.ldap.core.LdapTemplate;
-import org.springframework.ldap.core.support.BaseLdapPathContextSource;
-import org.springframework.security.ldap.LdapUsernameToDnMapper;
-import org.springframework.security.ldap.LdapUtils;
-
-/**
- * Wraps low-level LDAP operation on user and roles, used by
- * {@link ArgeoLdapUserDetailsManager}
- */
-public class ArgeoUserAdminDaoLdap {
- private String userBase;
- private String usernameAttribute;
- private String groupBase;
- private String[] groupClasses;
-
- private String groupRoleAttribute;
- private String groupMemberAttribute;
- private String defaultRole;
- private String rolePrefix;
-
- private final LdapTemplate ldapTemplate;
- private LdapUsernameToDnMapper usernameMapper;
-
- /**
- * Standard constructor, using the LDAP context source shared with Spring
- * Security components.
- */
- public ArgeoUserAdminDaoLdap(BaseLdapPathContextSource contextSource) {
- this.ldapTemplate = new LdapTemplate(contextSource);
- }
-
- @SuppressWarnings("unchecked")
- public synchronized Set<String> listUsers() {
- List<String> usernames = (List<String>) ldapTemplate.listBindings(
- new DistinguishedName(userBase), new ContextMapper() {
- public Object mapFromContext(Object ctxArg) {
- DirContextAdapter ctx = (DirContextAdapter) ctxArg;
- return ctx.getStringAttribute(usernameAttribute);
- }
- });
-
- return Collections
- .unmodifiableSortedSet(new TreeSet<String>(usernames));
- }
-
- @SuppressWarnings("unchecked")
- public Set<String> listEditableRoles() {
- return Collections.unmodifiableSortedSet(new TreeSet<String>(
- ldapTemplate.listBindings(groupBase, new ContextMapper() {
- public Object mapFromContext(Object ctxArg) {
- String groupName = ((DirContextAdapter) ctxArg)
- .getStringAttribute(groupRoleAttribute);
- String roleName = convertGroupToRole(groupName);
- return roleName;
- }
- })));
- }
-
- @SuppressWarnings("unchecked")
- public Set<String> listUsersInRole(String role) {
- return (Set<String>) ldapTemplate.lookup(
- buildGroupDn(convertRoleToGroup(role)), new ContextMapper() {
- public Object mapFromContext(Object ctxArg) {
- DirContextAdapter ctx = (DirContextAdapter) ctxArg;
- String[] userDns = ctx
- .getStringAttributes(groupMemberAttribute);
- TreeSet<String> set = new TreeSet<String>();
- for (String userDn : userDns) {
- DistinguishedName dn = new DistinguishedName(userDn);
- String username = dn.getValue(usernameAttribute);
- set.add(username);
- }
- return Collections.unmodifiableSortedSet(set);
- }
- });
- }
-
- public void createRole(String role, final String superuserName) {
- String group = convertRoleToGroup(role);
- DistinguishedName superuserDn = (DistinguishedName) ldapTemplate
- .executeReadWrite(new ContextExecutor() {
- public Object executeWithContext(DirContext ctx)
- throws NamingException {
- return LdapUtils.getFullDn(
- usernameMapper.buildDn(superuserName), ctx);
- }
- });
-
- Name groupDn = buildGroupDn(group);
- DirContextAdapter context = new DirContextAdapter();
- context.setAttributeValues("objectClass", groupClasses);
- context.setAttributeValue("cn", group);
- // Add superuser because cannot create empty group
- context.setAttributeValue(groupMemberAttribute, superuserDn.toString());
- ldapTemplate.bind(groupDn, context, null);
- }
-
- public void deleteRole(String role) {
- String group = convertRoleToGroup(role);
- Name dn = buildGroupDn(group);
- ldapTemplate.unbind(dn);
- }
-
- /** Maps a role (ROLE_XXX) to the related LDAP group (xxx) */
- protected String convertRoleToGroup(String role) {
- String group = role;
- if (group.startsWith(rolePrefix)) {
- group = group.substring(rolePrefix.length());
- group = group.toLowerCase();
- }
- return group;
- }
-
- /** Maps anLDAP group (xxx) to the related role (ROLE_XXX) */
- protected String convertGroupToRole(String groupName) {
- groupName = groupName.toUpperCase();
-
- return rolePrefix + groupName;
- }
-
- protected Name buildGroupDn(String name) {
- return new DistinguishedName(groupRoleAttribute + "=" + name + ","
- + groupBase);
- }
-
- public void setUserBase(String userBase) {
- this.userBase = userBase;
- }
-
- public void setUsernameAttribute(String usernameAttribute) {
- this.usernameAttribute = usernameAttribute;
- }
-
- public void setGroupBase(String groupBase) {
- this.groupBase = groupBase;
- }
-
- public void setGroupRoleAttribute(String groupRoleAttributeName) {
- this.groupRoleAttribute = groupRoleAttributeName;
- }
-
- public void setGroupMemberAttribute(String groupMemberAttributeName) {
- this.groupMemberAttribute = groupMemberAttributeName;
- }
-
- public void setDefaultRole(String defaultRole) {
- this.defaultRole = defaultRole;
- }
-
- public void setRolePrefix(String rolePrefix) {
- this.rolePrefix = rolePrefix;
- }
-
- public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
- this.usernameMapper = usernameMapper;
- }
-
- public String getDefaultRole() {
- return defaultRole;
- }
-
- public void setGroupClasses(String[] groupClasses) {
- this.groupClasses = groupClasses;
- }
-}
+++ /dev/null
-/*
- * 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.ldap.jcr;
-
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.SortedSet;
-import java.util.UUID;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.query.Query;
-import javax.jcr.version.VersionManager;
-import javax.naming.Name;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.ModificationItem;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.jcr.ArgeoTypes;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.security.SecurityUtils;
-import org.argeo.security.jcr.JcrSecurityModel;
-import org.argeo.security.jcr.JcrUserDetails;
-import org.argeo.security.jcr.SimpleJcrSecurityModel;
-import org.springframework.ldap.core.ContextMapper;
-import org.springframework.ldap.core.DirContextAdapter;
-import org.springframework.ldap.core.DirContextOperations;
-import org.springframework.ldap.core.DistinguishedName;
-import org.springframework.ldap.core.LdapTemplate;
-import org.springframework.security.GrantedAuthority;
-import org.springframework.security.ldap.LdapUsernameToDnMapper;
-import org.springframework.security.providers.encoding.PasswordEncoder;
-import org.springframework.security.userdetails.UserDetails;
-import org.springframework.security.userdetails.ldap.UserDetailsContextMapper;
-
-/** Makes sure that LDAP and JCR are in line. */
-public class JcrLdapSynchronizer implements UserDetailsContextMapper,
- ArgeoNames {
- private final static Log log = LogFactory.getLog(JcrLdapSynchronizer.class);
-
- // LDAP
- private LdapTemplate ldapTemplate;
- /**
- * LDAP template whose context source has an object factory set to null. see
- * <a href=
- * "http://forum.springsource.org/showthread.php?55955-Persistent-search-with-spring-ldap"
- * >this</a>
- */
- // private LdapTemplate rawLdapTemplate;
-
- private String userBase;
- private String usernameAttribute;
- private String passwordAttribute;
- private String[] userClasses;
- // private String defaultUserRole ="ROLE_USER";
-
- // private NamingListener ldapUserListener;
- // private SearchControls subTreeSearchControls;
- private LdapUsernameToDnMapper usernameMapper;
-
- private PasswordEncoder passwordEncoder;
- private final Random random;
-
- // JCR
- /** Admin session on the main workspace */
- private Session nodeSession;
- private Repository repository;
-
- // private JcrProfileListener jcrProfileListener;
- private JcrSecurityModel jcrSecurityModel = new SimpleJcrSecurityModel();
-
- // Mapping
- private Map<String, String> propertyToAttributes = new HashMap<String, String>();
-
- public JcrLdapSynchronizer() {
- random = createRandom();
- }
-
- public void init() {
- try {
- nodeSession = repository.login();
-
- // TODO put this in a different thread, and poll the LDAP server
- // until it is up
- try {
- synchronize();
-
- // LDAP
- // subTreeSearchControls = new SearchControls();
- // subTreeSearchControls
- // .setSearchScope(SearchControls.SUBTREE_SCOPE);
- // LDAP listener
- // ldapUserListener = new LdapUserListener();
- // rawLdapTemplate.executeReadOnly(new ContextExecutor() {
- // public Object executeWithContext(DirContext ctx)
- // throws NamingException {
- // EventDirContext ectx = (EventDirContext) ctx.lookup("");
- // ectx.addNamingListener(userBase, "("
- // + usernameAttribute + "=*)",
- // subTreeSearchControls, ldapUserListener);
- // return null;
- // }
- // });
- } catch (Exception e) {
- log.error("Could not synchronize and listen to LDAP,"
- + " probably because the LDAP server is not available."
- + " Restart the system as soon as possible.", e);
- }
-
- // JCR
- // String[] nodeTypes = { ArgeoTypes.ARGEO_USER_PROFILE };
- // jcrProfileListener = new JcrProfileListener();
- // noLocal is used so that we are not notified when we modify JCR
- // from LDAP
- // nodeSession
- // .getWorkspace()
- // .getObservationManager()
- // .addEventListener(jcrProfileListener,
- // Event.PROPERTY_CHANGED | Event.NODE_ADDED, "/",
- // true, null, nodeTypes, true);
- } catch (Exception e) {
- JcrUtils.logoutQuietly(nodeSession);
- throw new ArgeoException("Cannot initialize LDAP/JCR synchronizer",
- e);
- }
- }
-
- public void destroy() {
- // JcrUtils.removeListenerQuietly(nodeSession, jcrProfileListener);
- JcrUtils.logoutQuietly(nodeSession);
- // try {
- // rawLdapTemplate.executeReadOnly(new ContextExecutor() {
- // public Object executeWithContext(DirContext ctx)
- // throws NamingException {
- // EventDirContext ectx = (EventDirContext) ctx.lookup("");
- // ectx.removeNamingListener(ldapUserListener);
- // return null;
- // }
- // });
- // } catch (Exception e) {
- // // silent (LDAP server may have been shutdown already)
- // if (log.isTraceEnabled())
- // log.trace("Cannot remove LDAP listener", e);
- // }
- }
-
- /*
- * LDAP TO JCR
- */
- /** Full synchronization between LDAP and JCR. LDAP has priority. */
- protected void synchronize() {
- try {
- Name userBaseName = new DistinguishedName(userBase);
- // TODO subtree search?
- @SuppressWarnings("unchecked")
- List<String> userPaths = (List<String>) ldapTemplate.listBindings(
- userBaseName, new ContextMapper() {
- public Object mapFromContext(Object ctxObj) {
- try {
- return mapLdapToJcr((DirContextAdapter) ctxObj);
- } catch (Exception e) {
- // do not break process because of error
- log.error(
- "Could not LDAP->JCR synchronize user "
- + ctxObj, e);
- return null;
- }
- }
- });
-
- // create accounts which are not in LDAP
- Query query = nodeSession
- .getWorkspace()
- .getQueryManager()
- .createQuery(
- "select * from [" + ArgeoTypes.ARGEO_USER_PROFILE
- + "]", Query.JCR_SQL2);
- NodeIterator it = query.execute().getNodes();
- while (it.hasNext()) {
- Node userProfile = it.nextNode();
- String path = userProfile.getPath();
- try {
- if (!userPaths.contains(path)) {
- String username = userProfile
- .getProperty(ARGEO_USER_ID).getString();
- // GrantedAuthority[] authorities = {new
- // GrantedAuthorityImpl(defaultUserRole)};
- GrantedAuthority[] authorities = {};
- JcrUserDetails userDetails = new JcrUserDetails(
- userProfile, username, authorities);
- String dn = createLdapUser(userDetails);
- log.warn("Created ldap entry '" + dn + "' for user '"
- + username + "'");
-
- // if(!userProfile.getProperty(ARGEO_ENABLED).getBoolean()){
- // continue profiles;
- // }
- //
- // log.warn("Path "
- // + path
- // + " not found in LDAP, disabling user "
- // + userProfile.getProperty(ArgeoNames.ARGEO_USER_ID)
- // .getString());
-
- // Temporary hack to repair previous behaviour
- if (!userProfile.getProperty(ARGEO_ENABLED)
- .getBoolean()) {
- VersionManager versionManager = nodeSession
- .getWorkspace().getVersionManager();
- versionManager.checkout(userProfile.getPath());
- userProfile.setProperty(ArgeoNames.ARGEO_ENABLED,
- true);
- nodeSession.save();
- versionManager.checkin(userProfile.getPath());
- }
- }
- } catch (Exception e) {
- log.error("Cannot process " + path, e);
- }
- }
- } catch (Exception e) {
- JcrUtils.discardQuietly(nodeSession);
- log.error("Cannot synchronize LDAP and JCR", e);
- // throw new ArgeoException("Cannot synchronize LDAP and JCR", e);
- }
- }
-
- private String createLdapUser(UserDetails user) {
- DirContextAdapter ctx = new DirContextAdapter();
- mapUserToContext(user, ctx);
- DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
- ldapTemplate.bind(dn, ctx, null);
- return dn.toString();
- }
-
- /** Called during authentication in order to retrieve user details */
- public UserDetails mapUserFromContext(final DirContextOperations ctx,
- final String username, GrantedAuthority[] authorities) {
- if (ctx == null)
- throw new ArgeoException("No LDAP information for user " + username);
-
- String ldapUsername = ctx.getStringAttribute(usernameAttribute);
- if (!ldapUsername.equals(username))
- throw new ArgeoException("Logged in with username " + username
- + " but LDAP user is " + ldapUsername);
-
- Node userProfile = jcrSecurityModel.sync(nodeSession, username,
- SecurityUtils.authoritiesToStringList(authorities));
- // JcrUserDetails.checkAccountStatus(userProfile);
-
- // password
- SortedSet<?> passwordAttributes = ctx
- .getAttributeSortedStringSet(passwordAttribute);
- String password;
- if (passwordAttributes == null || passwordAttributes.size() == 0) {
- //throw new ArgeoException("No password found for user " + username);
- password = "NULL";
- } else {
- byte[] arr = (byte[]) passwordAttributes.first();
- password = new String(arr);
- // erase password
- Arrays.fill(arr, (byte) 0);
- }
-
- try {
- return new JcrUserDetails(userProfile, password, authorities);
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot retrieve user details for "
- + username, e);
- }
- }
-
- /**
- * Writes an LDAP context to the JCR user profile.
- *
- * @return path to user profile
- */
- protected synchronized String mapLdapToJcr(DirContextAdapter ctx) {
- Session session = nodeSession;
- try {
- // process
- String username = ctx.getStringAttribute(usernameAttribute);
-
- Node userProfile = jcrSecurityModel.sync(session, username, null);
- Map<String, String> modifications = new HashMap<String, String>();
- for (String jcrProperty : propertyToAttributes.keySet())
- ldapToJcr(userProfile, jcrProperty, ctx, modifications);
-
- int modifCount = modifications.size();
- if (modifCount > 0) {
- session.getWorkspace().getVersionManager()
- .checkout(userProfile.getPath());
- for (String prop : modifications.keySet())
- userProfile.setProperty(prop, modifications.get(prop));
- JcrUtils.updateLastModified(userProfile);
- session.save();
- session.getWorkspace().getVersionManager()
- .checkin(userProfile.getPath());
- if (log.isDebugEnabled())
- log.debug("Mapped " + modifCount + " LDAP modification"
- + (modifCount == 1 ? "" : "s") + " from "
- + ctx.getDn() + " to " + userProfile);
- }
- return userProfile.getPath();
- } catch (Exception e) {
- JcrUtils.discardQuietly(session);
- throw new ArgeoException("Cannot synchronize JCR and LDAP", e);
- }
- }
-
- /** Maps an LDAP property to a JCR property */
- protected void ldapToJcr(Node userProfile, String jcrProperty,
- DirContextOperations ctx, Map<String, String> modifications) {
- // TODO do we really need DirContextOperations?
- 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);
- String jcrValue = userProfile.hasProperty(jcrProperty) ? userProfile
- .getProperty(jcrProperty).getString() : null;
- if (value != null && jcrValue != null) {
- if (!value.equals(jcrValue))
- modifications.put(jcrProperty, value);
- } else if (value != null && jcrValue == null) {
- modifications.put(jcrProperty, value);
- } else if (value == null && jcrValue != null) {
- modifications.put(jcrProperty, value);
- }
- } catch (Exception e) {
- throw new ArgeoException("Cannot map JCR property " + jcrProperty
- + " from LDAP", e);
- }
- }
-
- /*
- * JCR to LDAP
- */
-
- public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) {
- if (!(user instanceof JcrUserDetails))
- throw new ArgeoException("Unsupported user details: "
- + user.getClass());
-
- ctx.setAttributeValues("objectClass", userClasses);
- ctx.setAttributeValue(usernameAttribute, user.getUsername());
- ctx.setAttributeValue(passwordAttribute,
- encodePassword(user.getPassword()));
-
- final JcrUserDetails jcrUserDetails = (JcrUserDetails) user;
- try {
- Node userProfile = nodeSession
- .getNode(jcrUserDetails.getHomePath()).getNode(
- ARGEO_PROFILE);
- for (String jcrProperty : propertyToAttributes.keySet()) {
- if (userProfile.hasProperty(jcrProperty)) {
- ModificationItem mi = jcrToLdap(jcrProperty, userProfile
- .getProperty(jcrProperty).getString());
- if (mi != null)
- ctx.setAttribute(mi.getAttribute());
- }
- }
- if (log.isTraceEnabled())
- log.trace("Mapped " + userProfile + " to " + ctx.getDn());
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot synchronize JCR and LDAP", e);
- }
-
- }
-
- /** Maps a JCR property to an LDAP property */
- protected ModificationItem jcrToLdap(String jcrProperty, String value) {
- // TODO do we really need DirContextOperations?
- try {
- String ldapAttribute;
- if (propertyToAttributes.containsKey(jcrProperty))
- ldapAttribute = propertyToAttributes.get(jcrProperty);
- else
- return null;
-
- // fix issue with empty 'sn' in LDAP
- if (ldapAttribute.equals("sn") && (value.trim().equals("")))
- return null;
- // fix issue with empty 'description' in LDAP
- if (ldapAttribute.equals("description") && value.trim().equals(""))
- return null;
- BasicAttribute attr = new BasicAttribute(
- propertyToAttributes.get(jcrProperty), value);
- ModificationItem mi = new ModificationItem(
- DirContext.REPLACE_ATTRIBUTE, attr);
- return mi;
- } catch (Exception e) {
- throw new ArgeoException("Cannot map JCR property " + jcrProperty
- + " from LDAP", e);
- }
- }
-
- /*
- * UTILITIES
- */
- protected String encodePassword(String password) {
- if (!password.startsWith("{")) {
- byte[] salt = new byte[16];
- random.nextBytes(salt);
- return passwordEncoder.encodePassword(password, salt);
- } else {
- return password;
- }
- }
-
- private static Random createRandom() {
- try {
- return SecureRandom.getInstance("SHA1PRNG");
- } catch (NoSuchAlgorithmException e) {
- return new Random(System.currentTimeMillis());
- }
- }
-
- /*
- * DEPENDENCY INJECTION
- */
-
- public void setLdapTemplate(LdapTemplate ldapTemplate) {
- this.ldapTemplate = ldapTemplate;
- }
-
- public void setRawLdapTemplate(LdapTemplate rawLdapTemplate) {
- // this.rawLdapTemplate = rawLdapTemplate;
- }
-
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- public void setUserBase(String userBase) {
- this.userBase = userBase;
- }
-
- public void setUsernameAttribute(String usernameAttribute) {
- this.usernameAttribute = usernameAttribute;
- }
-
- public void setPropertyToAttributes(Map<String, String> propertyToAttributes) {
- this.propertyToAttributes = propertyToAttributes;
- }
-
- public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
- this.usernameMapper = usernameMapper;
- }
-
- public void setPasswordAttribute(String passwordAttribute) {
- this.passwordAttribute = passwordAttribute;
- }
-
- public void setUserClasses(String[] userClasses) {
- this.userClasses = userClasses;
- }
-
- public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
- this.passwordEncoder = passwordEncoder;
- }
-
- public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) {
- this.jcrSecurityModel = jcrSecurityModel;
- }
-
- /** Listen to LDAP */
- // class LdapUserListener implements ObjectChangeListener,
- // NamespaceChangeListener, UnsolicitedNotificationListener {
- //
- // public void namingExceptionThrown(NamingExceptionEvent evt) {
- // evt.getException().printStackTrace();
- // }
- //
- // public void objectChanged(NamingEvent evt) {
- // Binding user = evt.getNewBinding();
- // // TODO find a way not to be called when JCR is the source of the
- // // modification
- // DirContextAdapter ctx = (DirContextAdapter) ldapTemplate
- // .lookup(user.getName());
- // mapLdapToJcr(ctx);
- // }
- //
- // public void objectAdded(NamingEvent evt) {
- // Binding user = evt.getNewBinding();
- // DirContextAdapter ctx = (DirContextAdapter) ldapTemplate
- // .lookup(user.getName());
- // mapLdapToJcr(ctx);
- // }
- //
- // public void objectRemoved(NamingEvent evt) {
- // if (log.isDebugEnabled())
- // log.debug(evt);
- // }
- //
- // public void objectRenamed(NamingEvent evt) {
- // if (log.isDebugEnabled())
- // log.debug(evt);
- // }
- //
- // public void notificationReceived(UnsolicitedNotificationEvent evt) {
- // UnsolicitedNotification notification = evt.getNotification();
- // NamingException ne = notification.getException();
- // String msg = "LDAP notification " + "ID=" + notification.getID()
- // + ", referrals=" + notification.getReferrals();
- // if (ne != null) {
- // if (log.isTraceEnabled())
- // log.trace(msg + ", exception= " + ne, ne);
- // else
- // log.warn(msg + ", exception= " + ne);
- // } else if (log.isDebugEnabled()) {
- // log.debug("Unsollicited LDAP notification " + msg);
- // }
- // }
- //
- // }
-
- /** Listen to JCR */
- // class JcrProfileListener implements EventListener {
- //
- // public void onEvent(EventIterator events) {
- // try {
- // final Map<Name, List<ModificationItem>> modifications = new HashMap<Name,
- // List<ModificationItem>>();
- // while (events.hasNext()) {
- // Event event = events.nextEvent();
- // try {
- // if (Event.PROPERTY_CHANGED == event.getType()) {
- // Property property = (Property) nodeSession
- // .getItem(event.getPath());
- // String propertyName = property.getName();
- // Node userProfile = property.getParent();
- // String username = userProfile.getProperty(
- // ARGEO_USER_ID).getString();
- // if (propertyToAttributes.containsKey(propertyName)) {
- // Name name = usernameMapper.buildDn(username);
- // if (!modifications.containsKey(name))
- // modifications.put(name,
- // new ArrayList<ModificationItem>());
- // String value = property.getString();
- // ModificationItem mi = jcrToLdap(propertyName,
- // value);
- // if (mi != null)
- // modifications.get(name).add(mi);
- // }
- // } else if (Event.NODE_ADDED == event.getType()) {
- // Node userProfile = nodeSession.getNode(event
- // .getPath());
- // String username = userProfile.getProperty(
- // ARGEO_USER_ID).getString();
- // Name name = usernameMapper.buildDn(username);
- // for (String propertyName : propertyToAttributes
- // .keySet()) {
- // if (!modifications.containsKey(name))
- // modifications.put(name,
- // new ArrayList<ModificationItem>());
- // String value = userProfile.getProperty(
- // propertyName).getString();
- // ModificationItem mi = jcrToLdap(propertyName,
- // value);
- // if (mi != null)
- // modifications.get(name).add(mi);
- // }
- // }
- // } catch (RepositoryException e) {
- // throw new ArgeoException("Cannot process event "
- // + event, e);
- // }
- // }
- //
- // for (Name name : modifications.keySet()) {
- // List<ModificationItem> userModifs = modifications.get(name);
- // int modifCount = userModifs.size();
- // ldapTemplate.modifyAttributes(name, userModifs
- // .toArray(new ModificationItem[modifCount]));
- // if (log.isDebugEnabled())
- // log.debug("Mapped " + modifCount + " JCR modification"
- // + (modifCount == 1 ? "" : "s") + " to " + name);
- // }
- // } catch (Exception e) {
- // // if (log.isDebugEnabled())
- // // e.printStackTrace();
- // throw new ArgeoException("Cannot process JCR events ("
- // + e.getMessage() + ")", e);
- // }
- // }
- //
- // }
-}
+++ /dev/null
-/*
- * 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.ldap.jcr;
-
-import java.util.UUID;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.UserJcrUtils;
-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;
-
-/** @deprecated Read only mapping from LDAP to user details */
-@Deprecated
-public class JcrUserDetailsContextMapper implements UserDetailsContextMapper,
- ArgeoNames {
- /** Admin session on the security workspace */
- private Session securitySession;
- private Repository repository;
- private String securityWorkspace = "security";
-
- public void init() {
- try {
- securitySession = repository.login(securityWorkspace);
- } catch (RepositoryException e) {
- JcrUtils.logoutQuietly(securitySession);
- throw new ArgeoException(
- "Cannot initialize LDAP/JCR user details context mapper", e);
- }
- }
-
- public void destroy() {
- JcrUtils.logoutQuietly(securitySession);
- }
-
- /** Called during authentication in order to retrieve user details */
- public UserDetails mapUserFromContext(final DirContextOperations ctx,
- final String username, GrantedAuthority[] authorities) {
- if (ctx == null)
- throw new ArgeoException("No LDAP information for user " + username);
- Node userHome = UserJcrUtils.getUserHome(securitySession, username);
- if (userHome == null)
- throw new ArgeoException("No JCR information for user " + username);
-
- // password
- // 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);
- // }
-
- try {
- // we don't have access to password, so let's not pretend
- String password = UUID.randomUUID().toString();
- return new JcrUserDetails(userHome.getNode(ARGEO_PROFILE),
- password, authorities);
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot retrieve user details for "
- + username, e);
- }
- }
-
- public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) {
- throw new UnsupportedOperationException("LDAP access is read-only");
- }
-
-}
--- /dev/null
+/*
+ * 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.ldap;
+
+import org.springframework.security.providers.ldap.authenticator.LdapShaPasswordEncoder;
+
+/**
+ * {@link LdapShaPasswordEncoder} allowing to configure the usage of salt (APache
+ * Directory Server 1.0 does not support bind with SSHA)
+ */
+public class ArgeoLdapShaPasswordEncoder extends LdapShaPasswordEncoder {
+ private Boolean useSalt = true;
+
+ @Override
+ public String encodePassword(String rawPass, Object salt) {
+ return super.encodePassword(rawPass, useSalt ? salt : null);
+ }
+
+ public void setUseSalt(Boolean useSalt) {
+ this.useSalt = useSalt;
+ }
+
+}
--- /dev/null
+/*
+ * 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.ldap;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.argeo.ArgeoException;
+import org.argeo.security.UserAdminService;
+import org.springframework.ldap.core.ContextSource;
+import org.springframework.security.Authentication;
+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.LdapUserDetailsManager;
+
+/** Extends {@link LdapUserDetailsManager} by adding password encoding support. */
+public class ArgeoLdapUserDetailsManager extends LdapUserDetailsManager
+ implements UserAdminService {
+ private String superUsername = "root";
+ private ArgeoUserAdminDaoLdap userAdminDao;
+ private PasswordEncoder passwordEncoder;
+ private final Random random;
+
+ public ArgeoLdapUserDetailsManager(ContextSource contextSource) {
+ super(contextSource);
+ this.random = createRandom();
+ }
+
+ private static Random createRandom() {
+ try {
+ return SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ return new Random(System.currentTimeMillis());
+ }
+ }
+
+ @Override
+ public void changePassword(String oldPassword, String newPassword) {
+ Authentication authentication = SecurityContextHolder.getContext()
+ .getAuthentication();
+ if (authentication == null)
+ throw new ArgeoException(
+ "Cannot change password without authentication");
+ String username = authentication.getName();
+ UserDetails userDetails = loadUserByUsername(username);
+ String currentPassword = userDetails.getPassword();
+ if (currentPassword == null)
+ throw new ArgeoException("Cannot access current password");
+ if (!passwordEncoder
+ .isPasswordValid(currentPassword, oldPassword, null))
+ throw new ArgeoException("Old password invalid");
+ // Spring Security LDAP 2.0 is buggy when used with OpenLDAP and called
+ // with oldPassword argument
+ super.changePassword(null, encodePassword(newPassword));
+ }
+
+ public void newRole(String role) {
+ userAdminDao.createRole(role, superUsername);
+ }
+
+ public void synchronize() {
+ for (String username : userAdminDao.listUsers())
+ loadUserByUsername(username);
+ // TODO: find a way to remove from JCR
+ }
+
+ public void deleteRole(String role) {
+ userAdminDao.deleteRole(role);
+ }
+
+ public Set<String> listUsers() {
+ return userAdminDao.listUsers();
+ }
+
+ public Set<String> listUsersInRole(String role) {
+ Set<String> lst = new TreeSet<String>(
+ userAdminDao.listUsersInRole(role));
+ Iterator<String> it = lst.iterator();
+ while (it.hasNext()) {
+ if (it.next().equals(superUsername)) {
+ it.remove();
+ break;
+ }
+ }
+ return lst;
+ }
+
+ public List<String> listUserRoles(String username) {
+ UserDetails userDetails = loadUserByUsername(username);
+ List<String> roles = new ArrayList<String>();
+ for (GrantedAuthority ga : userDetails.getAuthorities()) {
+ roles.add(ga.getAuthority());
+ }
+ return Collections.unmodifiableList(roles);
+ }
+
+ public Set<String> listEditableRoles() {
+ return userAdminDao.listEditableRoles();
+ }
+
+ protected String encodePassword(String password) {
+ if (!password.startsWith("{")) {
+ byte[] salt = new byte[16];
+ random.nextBytes(salt);
+ return passwordEncoder.encodePassword(password, salt);
+ } else {
+ return password;
+ }
+ }
+
+ public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
+ this.passwordEncoder = passwordEncoder;
+ }
+
+ public void setSuperUsername(String superUsername) {
+ this.superUsername = superUsername;
+ }
+
+ public void setUserAdminDao(ArgeoUserAdminDaoLdap userAdminDao) {
+ this.userAdminDao = userAdminDao;
+ }
+
+}
--- /dev/null
+/*
+ * 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.ldap;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+
+import org.springframework.ldap.core.ContextExecutor;
+import org.springframework.ldap.core.ContextMapper;
+import org.springframework.ldap.core.DirContextAdapter;
+import org.springframework.ldap.core.DistinguishedName;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.core.support.BaseLdapPathContextSource;
+import org.springframework.security.ldap.LdapUsernameToDnMapper;
+import org.springframework.security.ldap.LdapUtils;
+
+/**
+ * Wraps low-level LDAP operation on user and roles, used by
+ * {@link ArgeoLdapUserDetailsManager}
+ */
+public class ArgeoUserAdminDaoLdap {
+ private String userBase;
+ private String usernameAttribute;
+ private String groupBase;
+ private String[] groupClasses;
+
+ private String groupRoleAttribute;
+ private String groupMemberAttribute;
+ private String defaultRole;
+ private String rolePrefix;
+
+ private final LdapTemplate ldapTemplate;
+ private LdapUsernameToDnMapper usernameMapper;
+
+ /**
+ * Standard constructor, using the LDAP context source shared with Spring
+ * Security components.
+ */
+ public ArgeoUserAdminDaoLdap(BaseLdapPathContextSource contextSource) {
+ this.ldapTemplate = new LdapTemplate(contextSource);
+ }
+
+ @SuppressWarnings("unchecked")
+ public synchronized Set<String> listUsers() {
+ List<String> usernames = (List<String>) ldapTemplate.listBindings(
+ new DistinguishedName(userBase), new ContextMapper() {
+ public Object mapFromContext(Object ctxArg) {
+ DirContextAdapter ctx = (DirContextAdapter) ctxArg;
+ return ctx.getStringAttribute(usernameAttribute);
+ }
+ });
+
+ return Collections
+ .unmodifiableSortedSet(new TreeSet<String>(usernames));
+ }
+
+ @SuppressWarnings("unchecked")
+ public Set<String> listEditableRoles() {
+ return Collections.unmodifiableSortedSet(new TreeSet<String>(
+ ldapTemplate.listBindings(groupBase, new ContextMapper() {
+ public Object mapFromContext(Object ctxArg) {
+ String groupName = ((DirContextAdapter) ctxArg)
+ .getStringAttribute(groupRoleAttribute);
+ String roleName = convertGroupToRole(groupName);
+ return roleName;
+ }
+ })));
+ }
+
+ @SuppressWarnings("unchecked")
+ public Set<String> listUsersInRole(String role) {
+ return (Set<String>) ldapTemplate.lookup(
+ buildGroupDn(convertRoleToGroup(role)), new ContextMapper() {
+ public Object mapFromContext(Object ctxArg) {
+ DirContextAdapter ctx = (DirContextAdapter) ctxArg;
+ String[] userDns = ctx
+ .getStringAttributes(groupMemberAttribute);
+ TreeSet<String> set = new TreeSet<String>();
+ for (String userDn : userDns) {
+ DistinguishedName dn = new DistinguishedName(userDn);
+ String username = dn.getValue(usernameAttribute);
+ set.add(username);
+ }
+ return Collections.unmodifiableSortedSet(set);
+ }
+ });
+ }
+
+ public void createRole(String role, final String superuserName) {
+ String group = convertRoleToGroup(role);
+ DistinguishedName superuserDn = (DistinguishedName) ldapTemplate
+ .executeReadWrite(new ContextExecutor() {
+ public Object executeWithContext(DirContext ctx)
+ throws NamingException {
+ return LdapUtils.getFullDn(
+ usernameMapper.buildDn(superuserName), ctx);
+ }
+ });
+
+ Name groupDn = buildGroupDn(group);
+ DirContextAdapter context = new DirContextAdapter();
+ context.setAttributeValues("objectClass", groupClasses);
+ context.setAttributeValue("cn", group);
+ // Add superuser because cannot create empty group
+ context.setAttributeValue(groupMemberAttribute, superuserDn.toString());
+ ldapTemplate.bind(groupDn, context, null);
+ }
+
+ public void deleteRole(String role) {
+ String group = convertRoleToGroup(role);
+ Name dn = buildGroupDn(group);
+ ldapTemplate.unbind(dn);
+ }
+
+ /** Maps a role (ROLE_XXX) to the related LDAP group (xxx) */
+ protected String convertRoleToGroup(String role) {
+ String group = role;
+ if (group.startsWith(rolePrefix)) {
+ group = group.substring(rolePrefix.length());
+ group = group.toLowerCase();
+ }
+ return group;
+ }
+
+ /** Maps anLDAP group (xxx) to the related role (ROLE_XXX) */
+ protected String convertGroupToRole(String groupName) {
+ groupName = groupName.toUpperCase();
+
+ return rolePrefix + groupName;
+ }
+
+ protected Name buildGroupDn(String name) {
+ return new DistinguishedName(groupRoleAttribute + "=" + name + ","
+ + groupBase);
+ }
+
+ public void setUserBase(String userBase) {
+ this.userBase = userBase;
+ }
+
+ public void setUsernameAttribute(String usernameAttribute) {
+ this.usernameAttribute = usernameAttribute;
+ }
+
+ public void setGroupBase(String groupBase) {
+ this.groupBase = groupBase;
+ }
+
+ public void setGroupRoleAttribute(String groupRoleAttributeName) {
+ this.groupRoleAttribute = groupRoleAttributeName;
+ }
+
+ public void setGroupMemberAttribute(String groupMemberAttributeName) {
+ this.groupMemberAttribute = groupMemberAttributeName;
+ }
+
+ public void setDefaultRole(String defaultRole) {
+ this.defaultRole = defaultRole;
+ }
+
+ public void setRolePrefix(String rolePrefix) {
+ this.rolePrefix = rolePrefix;
+ }
+
+ public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
+ this.usernameMapper = usernameMapper;
+ }
+
+ public String getDefaultRole() {
+ return defaultRole;
+ }
+
+ public void setGroupClasses(String[] groupClasses) {
+ this.groupClasses = groupClasses;
+ }
+}
--- /dev/null
+/*
+ * 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.ldap.jcr;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.SortedSet;
+import java.util.UUID;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.version.VersionManager;
+import javax.naming.Name;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoNames;
+import org.argeo.jcr.ArgeoTypes;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.security.SecurityUtils;
+import org.argeo.security.jcr.JcrSecurityModel;
+import org.argeo.security.jcr.JcrUserDetails;
+import org.argeo.security.jcr.SimpleJcrSecurityModel;
+import org.springframework.ldap.core.ContextMapper;
+import org.springframework.ldap.core.DirContextAdapter;
+import org.springframework.ldap.core.DirContextOperations;
+import org.springframework.ldap.core.DistinguishedName;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.security.GrantedAuthority;
+import org.springframework.security.ldap.LdapUsernameToDnMapper;
+import org.springframework.security.providers.encoding.PasswordEncoder;
+import org.springframework.security.userdetails.UserDetails;
+import org.springframework.security.userdetails.ldap.UserDetailsContextMapper;
+
+/** Makes sure that LDAP and JCR are in line. */
+public class JcrLdapSynchronizer implements UserDetailsContextMapper,
+ ArgeoNames {
+ private final static Log log = LogFactory.getLog(JcrLdapSynchronizer.class);
+
+ // LDAP
+ private LdapTemplate ldapTemplate;
+ /**
+ * LDAP template whose context source has an object factory set to null. see
+ * <a href=
+ * "http://forum.springsource.org/showthread.php?55955-Persistent-search-with-spring-ldap"
+ * >this</a>
+ */
+ // private LdapTemplate rawLdapTemplate;
+
+ private String userBase;
+ private String usernameAttribute;
+ private String passwordAttribute;
+ private String[] userClasses;
+ // private String defaultUserRole ="ROLE_USER";
+
+ // private NamingListener ldapUserListener;
+ // private SearchControls subTreeSearchControls;
+ private LdapUsernameToDnMapper usernameMapper;
+
+ private PasswordEncoder passwordEncoder;
+ private final Random random;
+
+ // JCR
+ /** Admin session on the main workspace */
+ private Session nodeSession;
+ private Repository repository;
+
+ // private JcrProfileListener jcrProfileListener;
+ private JcrSecurityModel jcrSecurityModel = new SimpleJcrSecurityModel();
+
+ // Mapping
+ private Map<String, String> propertyToAttributes = new HashMap<String, String>();
+
+ public JcrLdapSynchronizer() {
+ random = createRandom();
+ }
+
+ public void init() {
+ try {
+ nodeSession = repository.login();
+
+ // TODO put this in a different thread, and poll the LDAP server
+ // until it is up
+ try {
+ synchronize();
+
+ // LDAP
+ // subTreeSearchControls = new SearchControls();
+ // subTreeSearchControls
+ // .setSearchScope(SearchControls.SUBTREE_SCOPE);
+ // LDAP listener
+ // ldapUserListener = new LdapUserListener();
+ // rawLdapTemplate.executeReadOnly(new ContextExecutor() {
+ // public Object executeWithContext(DirContext ctx)
+ // throws NamingException {
+ // EventDirContext ectx = (EventDirContext) ctx.lookup("");
+ // ectx.addNamingListener(userBase, "("
+ // + usernameAttribute + "=*)",
+ // subTreeSearchControls, ldapUserListener);
+ // return null;
+ // }
+ // });
+ } catch (Exception e) {
+ log.error("Could not synchronize and listen to LDAP,"
+ + " probably because the LDAP server is not available."
+ + " Restart the system as soon as possible.", e);
+ }
+
+ // JCR
+ // String[] nodeTypes = { ArgeoTypes.ARGEO_USER_PROFILE };
+ // jcrProfileListener = new JcrProfileListener();
+ // noLocal is used so that we are not notified when we modify JCR
+ // from LDAP
+ // nodeSession
+ // .getWorkspace()
+ // .getObservationManager()
+ // .addEventListener(jcrProfileListener,
+ // Event.PROPERTY_CHANGED | Event.NODE_ADDED, "/",
+ // true, null, nodeTypes, true);
+ } catch (Exception e) {
+ JcrUtils.logoutQuietly(nodeSession);
+ throw new ArgeoException("Cannot initialize LDAP/JCR synchronizer",
+ e);
+ }
+ }
+
+ public void destroy() {
+ // JcrUtils.removeListenerQuietly(nodeSession, jcrProfileListener);
+ JcrUtils.logoutQuietly(nodeSession);
+ // try {
+ // rawLdapTemplate.executeReadOnly(new ContextExecutor() {
+ // public Object executeWithContext(DirContext ctx)
+ // throws NamingException {
+ // EventDirContext ectx = (EventDirContext) ctx.lookup("");
+ // ectx.removeNamingListener(ldapUserListener);
+ // return null;
+ // }
+ // });
+ // } catch (Exception e) {
+ // // silent (LDAP server may have been shutdown already)
+ // if (log.isTraceEnabled())
+ // log.trace("Cannot remove LDAP listener", e);
+ // }
+ }
+
+ /*
+ * LDAP TO JCR
+ */
+ /** Full synchronization between LDAP and JCR. LDAP has priority. */
+ protected void synchronize() {
+ try {
+ Name userBaseName = new DistinguishedName(userBase);
+ // TODO subtree search?
+ @SuppressWarnings("unchecked")
+ List<String> userPaths = (List<String>) ldapTemplate.listBindings(
+ userBaseName, new ContextMapper() {
+ public Object mapFromContext(Object ctxObj) {
+ try {
+ return mapLdapToJcr((DirContextAdapter) ctxObj);
+ } catch (Exception e) {
+ // do not break process because of error
+ log.error(
+ "Could not LDAP->JCR synchronize user "
+ + ctxObj, e);
+ return null;
+ }
+ }
+ });
+
+ // create accounts which are not in LDAP
+ Query query = nodeSession
+ .getWorkspace()
+ .getQueryManager()
+ .createQuery(
+ "select * from [" + ArgeoTypes.ARGEO_USER_PROFILE
+ + "]", Query.JCR_SQL2);
+ NodeIterator it = query.execute().getNodes();
+ while (it.hasNext()) {
+ Node userProfile = it.nextNode();
+ String path = userProfile.getPath();
+ try {
+ if (!userPaths.contains(path)) {
+ String username = userProfile
+ .getProperty(ARGEO_USER_ID).getString();
+ // GrantedAuthority[] authorities = {new
+ // GrantedAuthorityImpl(defaultUserRole)};
+ GrantedAuthority[] authorities = {};
+ JcrUserDetails userDetails = new JcrUserDetails(
+ userProfile, username, authorities);
+ String dn = createLdapUser(userDetails);
+ log.warn("Created ldap entry '" + dn + "' for user '"
+ + username + "'");
+
+ // if(!userProfile.getProperty(ARGEO_ENABLED).getBoolean()){
+ // continue profiles;
+ // }
+ //
+ // log.warn("Path "
+ // + path
+ // + " not found in LDAP, disabling user "
+ // + userProfile.getProperty(ArgeoNames.ARGEO_USER_ID)
+ // .getString());
+
+ // Temporary hack to repair previous behaviour
+ if (!userProfile.getProperty(ARGEO_ENABLED)
+ .getBoolean()) {
+ VersionManager versionManager = nodeSession
+ .getWorkspace().getVersionManager();
+ versionManager.checkout(userProfile.getPath());
+ userProfile.setProperty(ArgeoNames.ARGEO_ENABLED,
+ true);
+ nodeSession.save();
+ versionManager.checkin(userProfile.getPath());
+ }
+ }
+ } catch (Exception e) {
+ log.error("Cannot process " + path, e);
+ }
+ }
+ } catch (Exception e) {
+ JcrUtils.discardQuietly(nodeSession);
+ log.error("Cannot synchronize LDAP and JCR", e);
+ // throw new ArgeoException("Cannot synchronize LDAP and JCR", e);
+ }
+ }
+
+ private String createLdapUser(UserDetails user) {
+ DirContextAdapter ctx = new DirContextAdapter();
+ mapUserToContext(user, ctx);
+ DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
+ ldapTemplate.bind(dn, ctx, null);
+ return dn.toString();
+ }
+
+ /** Called during authentication in order to retrieve user details */
+ public UserDetails mapUserFromContext(final DirContextOperations ctx,
+ final String username, GrantedAuthority[] authorities) {
+ if (ctx == null)
+ throw new ArgeoException("No LDAP information for user " + username);
+
+ String ldapUsername = ctx.getStringAttribute(usernameAttribute);
+ if (!ldapUsername.equals(username))
+ throw new ArgeoException("Logged in with username " + username
+ + " but LDAP user is " + ldapUsername);
+
+ Node userProfile = jcrSecurityModel.sync(nodeSession, username,
+ SecurityUtils.authoritiesToStringList(authorities));
+ // JcrUserDetails.checkAccountStatus(userProfile);
+
+ // password
+ SortedSet<?> passwordAttributes = ctx
+ .getAttributeSortedStringSet(passwordAttribute);
+ String password;
+ if (passwordAttributes == null || passwordAttributes.size() == 0) {
+ //throw new ArgeoException("No password found for user " + username);
+ password = "NULL";
+ } else {
+ byte[] arr = (byte[]) passwordAttributes.first();
+ password = new String(arr);
+ // erase password
+ Arrays.fill(arr, (byte) 0);
+ }
+
+ try {
+ return new JcrUserDetails(userProfile, password, authorities);
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot retrieve user details for "
+ + username, e);
+ }
+ }
+
+ /**
+ * Writes an LDAP context to the JCR user profile.
+ *
+ * @return path to user profile
+ */
+ protected synchronized String mapLdapToJcr(DirContextAdapter ctx) {
+ Session session = nodeSession;
+ try {
+ // process
+ String username = ctx.getStringAttribute(usernameAttribute);
+
+ Node userProfile = jcrSecurityModel.sync(session, username, null);
+ Map<String, String> modifications = new HashMap<String, String>();
+ for (String jcrProperty : propertyToAttributes.keySet())
+ ldapToJcr(userProfile, jcrProperty, ctx, modifications);
+
+ int modifCount = modifications.size();
+ if (modifCount > 0) {
+ session.getWorkspace().getVersionManager()
+ .checkout(userProfile.getPath());
+ for (String prop : modifications.keySet())
+ userProfile.setProperty(prop, modifications.get(prop));
+ JcrUtils.updateLastModified(userProfile);
+ session.save();
+ session.getWorkspace().getVersionManager()
+ .checkin(userProfile.getPath());
+ if (log.isDebugEnabled())
+ log.debug("Mapped " + modifCount + " LDAP modification"
+ + (modifCount == 1 ? "" : "s") + " from "
+ + ctx.getDn() + " to " + userProfile);
+ }
+ return userProfile.getPath();
+ } catch (Exception e) {
+ JcrUtils.discardQuietly(session);
+ throw new ArgeoException("Cannot synchronize JCR and LDAP", e);
+ }
+ }
+
+ /** Maps an LDAP property to a JCR property */
+ protected void ldapToJcr(Node userProfile, String jcrProperty,
+ DirContextOperations ctx, Map<String, String> modifications) {
+ // TODO do we really need DirContextOperations?
+ 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);
+ String jcrValue = userProfile.hasProperty(jcrProperty) ? userProfile
+ .getProperty(jcrProperty).getString() : null;
+ if (value != null && jcrValue != null) {
+ if (!value.equals(jcrValue))
+ modifications.put(jcrProperty, value);
+ } else if (value != null && jcrValue == null) {
+ modifications.put(jcrProperty, value);
+ } else if (value == null && jcrValue != null) {
+ modifications.put(jcrProperty, value);
+ }
+ } catch (Exception e) {
+ throw new ArgeoException("Cannot map JCR property " + jcrProperty
+ + " from LDAP", e);
+ }
+ }
+
+ /*
+ * JCR to LDAP
+ */
+
+ public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) {
+ if (!(user instanceof JcrUserDetails))
+ throw new ArgeoException("Unsupported user details: "
+ + user.getClass());
+
+ ctx.setAttributeValues("objectClass", userClasses);
+ ctx.setAttributeValue(usernameAttribute, user.getUsername());
+ ctx.setAttributeValue(passwordAttribute,
+ encodePassword(user.getPassword()));
+
+ final JcrUserDetails jcrUserDetails = (JcrUserDetails) user;
+ try {
+ Node userProfile = nodeSession
+ .getNode(jcrUserDetails.getHomePath()).getNode(
+ ARGEO_PROFILE);
+ for (String jcrProperty : propertyToAttributes.keySet()) {
+ if (userProfile.hasProperty(jcrProperty)) {
+ ModificationItem mi = jcrToLdap(jcrProperty, userProfile
+ .getProperty(jcrProperty).getString());
+ if (mi != null)
+ ctx.setAttribute(mi.getAttribute());
+ }
+ }
+ if (log.isTraceEnabled())
+ log.trace("Mapped " + userProfile + " to " + ctx.getDn());
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot synchronize JCR and LDAP", e);
+ }
+
+ }
+
+ /** Maps a JCR property to an LDAP property */
+ protected ModificationItem jcrToLdap(String jcrProperty, String value) {
+ // TODO do we really need DirContextOperations?
+ try {
+ String ldapAttribute;
+ if (propertyToAttributes.containsKey(jcrProperty))
+ ldapAttribute = propertyToAttributes.get(jcrProperty);
+ else
+ return null;
+
+ // fix issue with empty 'sn' in LDAP
+ if (ldapAttribute.equals("sn") && (value.trim().equals("")))
+ return null;
+ // fix issue with empty 'description' in LDAP
+ if (ldapAttribute.equals("description") && value.trim().equals(""))
+ return null;
+ BasicAttribute attr = new BasicAttribute(
+ propertyToAttributes.get(jcrProperty), value);
+ ModificationItem mi = new ModificationItem(
+ DirContext.REPLACE_ATTRIBUTE, attr);
+ return mi;
+ } catch (Exception e) {
+ throw new ArgeoException("Cannot map JCR property " + jcrProperty
+ + " from LDAP", e);
+ }
+ }
+
+ /*
+ * UTILITIES
+ */
+ protected String encodePassword(String password) {
+ if (!password.startsWith("{")) {
+ byte[] salt = new byte[16];
+ random.nextBytes(salt);
+ return passwordEncoder.encodePassword(password, salt);
+ } else {
+ return password;
+ }
+ }
+
+ private static Random createRandom() {
+ try {
+ return SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ return new Random(System.currentTimeMillis());
+ }
+ }
+
+ /*
+ * DEPENDENCY INJECTION
+ */
+
+ public void setLdapTemplate(LdapTemplate ldapTemplate) {
+ this.ldapTemplate = ldapTemplate;
+ }
+
+ public void setRawLdapTemplate(LdapTemplate rawLdapTemplate) {
+ // this.rawLdapTemplate = rawLdapTemplate;
+ }
+
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ public void setUserBase(String userBase) {
+ this.userBase = userBase;
+ }
+
+ public void setUsernameAttribute(String usernameAttribute) {
+ this.usernameAttribute = usernameAttribute;
+ }
+
+ public void setPropertyToAttributes(Map<String, String> propertyToAttributes) {
+ this.propertyToAttributes = propertyToAttributes;
+ }
+
+ public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
+ this.usernameMapper = usernameMapper;
+ }
+
+ public void setPasswordAttribute(String passwordAttribute) {
+ this.passwordAttribute = passwordAttribute;
+ }
+
+ public void setUserClasses(String[] userClasses) {
+ this.userClasses = userClasses;
+ }
+
+ public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
+ this.passwordEncoder = passwordEncoder;
+ }
+
+ public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) {
+ this.jcrSecurityModel = jcrSecurityModel;
+ }
+
+ /** Listen to LDAP */
+ // class LdapUserListener implements ObjectChangeListener,
+ // NamespaceChangeListener, UnsolicitedNotificationListener {
+ //
+ // public void namingExceptionThrown(NamingExceptionEvent evt) {
+ // evt.getException().printStackTrace();
+ // }
+ //
+ // public void objectChanged(NamingEvent evt) {
+ // Binding user = evt.getNewBinding();
+ // // TODO find a way not to be called when JCR is the source of the
+ // // modification
+ // DirContextAdapter ctx = (DirContextAdapter) ldapTemplate
+ // .lookup(user.getName());
+ // mapLdapToJcr(ctx);
+ // }
+ //
+ // public void objectAdded(NamingEvent evt) {
+ // Binding user = evt.getNewBinding();
+ // DirContextAdapter ctx = (DirContextAdapter) ldapTemplate
+ // .lookup(user.getName());
+ // mapLdapToJcr(ctx);
+ // }
+ //
+ // public void objectRemoved(NamingEvent evt) {
+ // if (log.isDebugEnabled())
+ // log.debug(evt);
+ // }
+ //
+ // public void objectRenamed(NamingEvent evt) {
+ // if (log.isDebugEnabled())
+ // log.debug(evt);
+ // }
+ //
+ // public void notificationReceived(UnsolicitedNotificationEvent evt) {
+ // UnsolicitedNotification notification = evt.getNotification();
+ // NamingException ne = notification.getException();
+ // String msg = "LDAP notification " + "ID=" + notification.getID()
+ // + ", referrals=" + notification.getReferrals();
+ // if (ne != null) {
+ // if (log.isTraceEnabled())
+ // log.trace(msg + ", exception= " + ne, ne);
+ // else
+ // log.warn(msg + ", exception= " + ne);
+ // } else if (log.isDebugEnabled()) {
+ // log.debug("Unsollicited LDAP notification " + msg);
+ // }
+ // }
+ //
+ // }
+
+ /** Listen to JCR */
+ // class JcrProfileListener implements EventListener {
+ //
+ // public void onEvent(EventIterator events) {
+ // try {
+ // final Map<Name, List<ModificationItem>> modifications = new HashMap<Name,
+ // List<ModificationItem>>();
+ // while (events.hasNext()) {
+ // Event event = events.nextEvent();
+ // try {
+ // if (Event.PROPERTY_CHANGED == event.getType()) {
+ // Property property = (Property) nodeSession
+ // .getItem(event.getPath());
+ // String propertyName = property.getName();
+ // Node userProfile = property.getParent();
+ // String username = userProfile.getProperty(
+ // ARGEO_USER_ID).getString();
+ // if (propertyToAttributes.containsKey(propertyName)) {
+ // Name name = usernameMapper.buildDn(username);
+ // if (!modifications.containsKey(name))
+ // modifications.put(name,
+ // new ArrayList<ModificationItem>());
+ // String value = property.getString();
+ // ModificationItem mi = jcrToLdap(propertyName,
+ // value);
+ // if (mi != null)
+ // modifications.get(name).add(mi);
+ // }
+ // } else if (Event.NODE_ADDED == event.getType()) {
+ // Node userProfile = nodeSession.getNode(event
+ // .getPath());
+ // String username = userProfile.getProperty(
+ // ARGEO_USER_ID).getString();
+ // Name name = usernameMapper.buildDn(username);
+ // for (String propertyName : propertyToAttributes
+ // .keySet()) {
+ // if (!modifications.containsKey(name))
+ // modifications.put(name,
+ // new ArrayList<ModificationItem>());
+ // String value = userProfile.getProperty(
+ // propertyName).getString();
+ // ModificationItem mi = jcrToLdap(propertyName,
+ // value);
+ // if (mi != null)
+ // modifications.get(name).add(mi);
+ // }
+ // }
+ // } catch (RepositoryException e) {
+ // throw new ArgeoException("Cannot process event "
+ // + event, e);
+ // }
+ // }
+ //
+ // for (Name name : modifications.keySet()) {
+ // List<ModificationItem> userModifs = modifications.get(name);
+ // int modifCount = userModifs.size();
+ // ldapTemplate.modifyAttributes(name, userModifs
+ // .toArray(new ModificationItem[modifCount]));
+ // if (log.isDebugEnabled())
+ // log.debug("Mapped " + modifCount + " JCR modification"
+ // + (modifCount == 1 ? "" : "s") + " to " + name);
+ // }
+ // } catch (Exception e) {
+ // // if (log.isDebugEnabled())
+ // // e.printStackTrace();
+ // throw new ArgeoException("Cannot process JCR events ("
+ // + e.getMessage() + ")", e);
+ // }
+ // }
+ //
+ // }
+}
--- /dev/null
+/*
+ * 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.ldap.jcr;
+
+import java.util.UUID;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoNames;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.UserJcrUtils;
+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;
+
+/** @deprecated Read only mapping from LDAP to user details */
+@Deprecated
+public class JcrUserDetailsContextMapper implements UserDetailsContextMapper,
+ ArgeoNames {
+ /** Admin session on the security workspace */
+ private Session securitySession;
+ private Repository repository;
+ private String securityWorkspace = "security";
+
+ public void init() {
+ try {
+ securitySession = repository.login(securityWorkspace);
+ } catch (RepositoryException e) {
+ JcrUtils.logoutQuietly(securitySession);
+ throw new ArgeoException(
+ "Cannot initialize LDAP/JCR user details context mapper", e);
+ }
+ }
+
+ public void destroy() {
+ JcrUtils.logoutQuietly(securitySession);
+ }
+
+ /** Called during authentication in order to retrieve user details */
+ public UserDetails mapUserFromContext(final DirContextOperations ctx,
+ final String username, GrantedAuthority[] authorities) {
+ if (ctx == null)
+ throw new ArgeoException("No LDAP information for user " + username);
+ Node userHome = UserJcrUtils.getUserHome(securitySession, username);
+ if (userHome == null)
+ throw new ArgeoException("No JCR information for user " + username);
+
+ // password
+ // 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);
+ // }
+
+ try {
+ // we don't have access to password, so let's not pretend
+ String password = UUID.randomUUID().toString();
+ return new JcrUserDetails(userHome.getNode(ARGEO_PROFILE),
+ password, authorities);
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot retrieve user details for "
+ + username, e);
+ }
+ }
+
+ public void mapUserToContext(UserDetails user, final DirContextAdapter ctx) {
+ throw new UnsupportedOperationException("LDAP access is read-only");
+ }
+
+}
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
- <classpathentry kind="src" output="target/classes" path="src/main/java"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
- <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
- <classpathentry kind="output" path="bin"/>
+ <classpathentry kind="src" path="src" />
+ <classpathentry kind="con"
+ path="org.eclipse.pde.core.requiredPlugins" />
+ <classpathentry kind="con"
+ path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" />
+ <classpathentry kind="output" path="bin" />
</classpath>
--- /dev/null
+Import-Package: javax.servlet,*
additional.bundles = org.springframework.beans
-source.. = src/main/java/
+source.. = src/
-<?xml version="1.0" encoding="UTF-8" standalone="no"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.argeo.commons</groupId>
</parent>
<artifactId>org.argeo.security.mvc</artifactId>
<name>Commons Security MVC</name>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-source-plugin</artifactId>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jar-plugin</artifactId>
- </plugin>
- <plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-bundle-plugin</artifactId>
-
- <configuration>
- <instructions>
- <Export-Package>
- org.argeo.security.mvc.*
- </Export-Package>
- <Import-Package>*,javax.servlet</Import-Package>
- </instructions>
- </configuration>
- </plugin>
- </plugins>
- </build>
<dependencies>
<!-- Argeo Server -->
<dependency>
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Mathieu Baudier
- *
- * 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.mvc;
-
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.springframework.security.ui.rememberme.TokenBasedRememberMeServices;
-
-public class ArgeoRememberMeServices extends TokenBasedRememberMeServices {
- public final static String DEFAULT_COOKIE_NAME = "ARGEO_SECURITY";
-
- public ArgeoRememberMeServices() {
- setCookieName(DEFAULT_COOKIE_NAME);
- }
-
- /**
- * Sets a "cancel cookie" (with maxAge = 0) on the response to disable
- * persistent logins.
- *
- * @param request
- * @param response
- */
- protected void cancelCookie(HttpServletRequest request,
- HttpServletResponse response) {
- Cookie cookie = new Cookie(getCookieName(), null);
- cookie.setMaxAge(0);
- cookie.setPath("/");
-
- response.addCookie(cookie);
- }
-
- /**
- * Sets the cookie on the response
- *
- * @param tokens
- * the tokens which will be encoded to make the cookie value.
- * @param maxAge
- * the value passed to {@link Cookie#setMaxAge(int)}
- * @param request
- * the request
- * @param response
- * the response to add the cookie to.
- */
- protected void setCookie(String[] tokens, int maxAge,
- HttpServletRequest request, HttpServletResponse response) {
- String cookieValue = encodeCookie(tokens);
- Cookie cookie = new Cookie(getCookieName(), cookieValue);
- cookie.setMaxAge(maxAge);
- cookie.setPath("/");
- response.addCookie(cookie);
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Mathieu Baudier
- *
- * 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.mvc;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.security.UserAdminService;
-import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
-
-/** Add the current argeo user as an attribute to the request. */
-public class ArgeoUserInterceptor extends HandlerInterceptorAdapter {
- private UserAdminService securityService;
-
- @Override
- public boolean preHandle(HttpServletRequest request,
- HttpServletResponse response, Object handler) throws Exception {
- //request.setAttribute("argeoUser", securityService.getCurrentUser());
- return super.preHandle(request, response, handler);
- }
-
- public void setSecurityService(UserAdminService securityService) {
- this.securityService = securityService;
- }
-
-}
+++ /dev/null
-/*
- * Copyright (C) 2007-2012 Mathieu Baudier
- *
- * 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.mvc;
-
-import org.argeo.server.mvc.MvcConstants;
-import org.springframework.stereotype.Controller;
-
-@Controller
-public class UsersRolesController implements MvcConstants {
-// private ArgeoSecurityService securityService;
-// private Deserializer userDeserializer = null;
-
- /* USER */
-
-// @RequestMapping("/getCredentials.*")
-// @ModelAttribute("user")
-// public ArgeoUser getCredentials() {
-// ArgeoUser argeoUser = securityService.getCurrentUser();
-// if (argeoUser == null)
-// return new SimpleArgeoUser();
-// else
-// return argeoUser;
-// }
-//
-// @RequestMapping("/getUsersList.*")
-// @ModelAttribute("users")
-// public Set<ArgeoUser> getUsersList() {
-// return securityService.listUsers();
-// }
-//
-// @RequestMapping("/userExists.*")
-// public BooleanAnswer userExists(@RequestParam("username") String username) {
-// return new BooleanAnswer(securityService.userExists(username));
-// }
-//
-// @RequestMapping("/createUser.*")
-// @ModelAttribute("user")
-// public ArgeoUser createUser(Reader reader) {
-// ArgeoUser user = userDeserializer.deserialize(reader,
-// SimpleArgeoUser.class);
-// securityService.newUser(user);
-// return securityService.getUser(user.getUsername());
-// }
-//
-// @RequestMapping("/updateUser.*")
-// @ModelAttribute("user")
-// public ArgeoUser updateUser(Reader reader) {
-// ArgeoUser user = userDeserializer.deserialize(reader,
-// SimpleArgeoUser.class);
-// securityService.updateUser(user);
-// return securityService.getUser(user.getUsername());
-// }
-//
-// @RequestMapping("/updateUserSelf.*")
-// @ModelAttribute("user")
-// /** Will only update the user natures.*/
-// public ArgeoUser updateUserSelf(Reader reader) {
-// ArgeoUser user = securityService.getCurrentUser();
-// ArgeoUser userForNatures = userDeserializer.deserialize(reader,
-// SimpleArgeoUser.class);
-// user.updateUserNatures(userForNatures.getUserNatures());
-// securityService.updateUser(user);
-// return securityService.getUser(user.getUsername());
-// }
-//
-// @RequestMapping("/deleteUser.*")
-// public ServerAnswer deleteUser(@RequestParam("username") String username) {
-// securityService.deleteUser(username);
-// return ServerAnswer.ok("User " + username + " deleted");
-// }
-//
-// @RequestMapping("/getUserDetails.*")
-// @ModelAttribute("user")
-// public ArgeoUser getUserDetails(@RequestParam("username") String username) {
-// return securityService.getUser(username);
-// }
-
- /* ROLE */
-// @RequestMapping("/getRolesList.*")
-// @ModelAttribute("roles")
-// public Set<String> getEditableRolesList() {
-// return securityService.listEditableRoles();
-// }
-//
-// @RequestMapping("/createRole.*")
-// public ServerAnswer createRole(@RequestParam("role") String role) {
-// securityService.newRole(role);
-// return ServerAnswer.ok("Role " + role + " created");
-// }
-//
-// @RequestMapping("/deleteRole.*")
-// public ServerAnswer deleteRole(@RequestParam("role") String role) {
-// securityService.deleteRole(role);
-// return ServerAnswer.ok("Role " + role + " deleted");
-// }
-//
-// @RequestMapping("/updateUserPassword.*")
-// public ServerAnswer updateUserPassword(
-// @RequestParam("username") String username,
-// @RequestParam("password") String password) {
-// securityService.updateUserPassword(username, password);
-// return ServerAnswer.ok("Password updated for user " + username);
-// }
-//
-// @RequestMapping("/updatePassword.*")
-// public ServerAnswer updatePassword(
-// @RequestParam("oldPassword") String oldPassword,
-// @RequestParam("password") String password) {
-// securityService.updateCurrentUserPassword(oldPassword, password);
-// return ServerAnswer.ok("Password updated");
-// }
-//
-// public void setUserDeserializer(Deserializer userDeserializer) {
-// this.userDeserializer = userDeserializer;
-// }
-//
-// public void setSecurityService(ArgeoSecurityService securityService) {
-// this.securityService = securityService;
-// }
-
-}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Mathieu Baudier
+ *
+ * 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.mvc;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.ui.rememberme.TokenBasedRememberMeServices;
+
+public class ArgeoRememberMeServices extends TokenBasedRememberMeServices {
+ public final static String DEFAULT_COOKIE_NAME = "ARGEO_SECURITY";
+
+ public ArgeoRememberMeServices() {
+ setCookieName(DEFAULT_COOKIE_NAME);
+ }
+
+ /**
+ * Sets a "cancel cookie" (with maxAge = 0) on the response to disable
+ * persistent logins.
+ *
+ * @param request
+ * @param response
+ */
+ protected void cancelCookie(HttpServletRequest request,
+ HttpServletResponse response) {
+ Cookie cookie = new Cookie(getCookieName(), null);
+ cookie.setMaxAge(0);
+ cookie.setPath("/");
+
+ response.addCookie(cookie);
+ }
+
+ /**
+ * Sets the cookie on the response
+ *
+ * @param tokens
+ * the tokens which will be encoded to make the cookie value.
+ * @param maxAge
+ * the value passed to {@link Cookie#setMaxAge(int)}
+ * @param request
+ * the request
+ * @param response
+ * the response to add the cookie to.
+ */
+ protected void setCookie(String[] tokens, int maxAge,
+ HttpServletRequest request, HttpServletResponse response) {
+ String cookieValue = encodeCookie(tokens);
+ Cookie cookie = new Cookie(getCookieName(), cookieValue);
+ cookie.setMaxAge(maxAge);
+ cookie.setPath("/");
+ response.addCookie(cookie);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Mathieu Baudier
+ *
+ * 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.mvc;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.security.UserAdminService;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+/** Add the current argeo user as an attribute to the request. */
+public class ArgeoUserInterceptor extends HandlerInterceptorAdapter {
+ private UserAdminService securityService;
+
+ @Override
+ public boolean preHandle(HttpServletRequest request,
+ HttpServletResponse response, Object handler) throws Exception {
+ //request.setAttribute("argeoUser", securityService.getCurrentUser());
+ return super.preHandle(request, response, handler);
+ }
+
+ public void setSecurityService(UserAdminService securityService) {
+ this.securityService = securityService;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Mathieu Baudier
+ *
+ * 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.mvc;
+
+import org.argeo.server.mvc.MvcConstants;
+import org.springframework.stereotype.Controller;
+
+@Controller
+public class UsersRolesController implements MvcConstants {
+// private ArgeoSecurityService securityService;
+// private Deserializer userDeserializer = null;
+
+ /* USER */
+
+// @RequestMapping("/getCredentials.*")
+// @ModelAttribute("user")
+// public ArgeoUser getCredentials() {
+// ArgeoUser argeoUser = securityService.getCurrentUser();
+// if (argeoUser == null)
+// return new SimpleArgeoUser();
+// else
+// return argeoUser;
+// }
+//
+// @RequestMapping("/getUsersList.*")
+// @ModelAttribute("users")
+// public Set<ArgeoUser> getUsersList() {
+// return securityService.listUsers();
+// }
+//
+// @RequestMapping("/userExists.*")
+// public BooleanAnswer userExists(@RequestParam("username") String username) {
+// return new BooleanAnswer(securityService.userExists(username));
+// }
+//
+// @RequestMapping("/createUser.*")
+// @ModelAttribute("user")
+// public ArgeoUser createUser(Reader reader) {
+// ArgeoUser user = userDeserializer.deserialize(reader,
+// SimpleArgeoUser.class);
+// securityService.newUser(user);
+// return securityService.getUser(user.getUsername());
+// }
+//
+// @RequestMapping("/updateUser.*")
+// @ModelAttribute("user")
+// public ArgeoUser updateUser(Reader reader) {
+// ArgeoUser user = userDeserializer.deserialize(reader,
+// SimpleArgeoUser.class);
+// securityService.updateUser(user);
+// return securityService.getUser(user.getUsername());
+// }
+//
+// @RequestMapping("/updateUserSelf.*")
+// @ModelAttribute("user")
+// /** Will only update the user natures.*/
+// public ArgeoUser updateUserSelf(Reader reader) {
+// ArgeoUser user = securityService.getCurrentUser();
+// ArgeoUser userForNatures = userDeserializer.deserialize(reader,
+// SimpleArgeoUser.class);
+// user.updateUserNatures(userForNatures.getUserNatures());
+// securityService.updateUser(user);
+// return securityService.getUser(user.getUsername());
+// }
+//
+// @RequestMapping("/deleteUser.*")
+// public ServerAnswer deleteUser(@RequestParam("username") String username) {
+// securityService.deleteUser(username);
+// return ServerAnswer.ok("User " + username + " deleted");
+// }
+//
+// @RequestMapping("/getUserDetails.*")
+// @ModelAttribute("user")
+// public ArgeoUser getUserDetails(@RequestParam("username") String username) {
+// return securityService.getUser(username);
+// }
+
+ /* ROLE */
+// @RequestMapping("/getRolesList.*")
+// @ModelAttribute("roles")
+// public Set<String> getEditableRolesList() {
+// return securityService.listEditableRoles();
+// }
+//
+// @RequestMapping("/createRole.*")
+// public ServerAnswer createRole(@RequestParam("role") String role) {
+// securityService.newRole(role);
+// return ServerAnswer.ok("Role " + role + " created");
+// }
+//
+// @RequestMapping("/deleteRole.*")
+// public ServerAnswer deleteRole(@RequestParam("role") String role) {
+// securityService.deleteRole(role);
+// return ServerAnswer.ok("Role " + role + " deleted");
+// }
+//
+// @RequestMapping("/updateUserPassword.*")
+// public ServerAnswer updateUserPassword(
+// @RequestParam("username") String username,
+// @RequestParam("password") String password) {
+// securityService.updateUserPassword(username, password);
+// return ServerAnswer.ok("Password updated for user " + username);
+// }
+//
+// @RequestMapping("/updatePassword.*")
+// public ServerAnswer updatePassword(
+// @RequestParam("oldPassword") String oldPassword,
+// @RequestParam("password") String password) {
+// securityService.updateCurrentUserPassword(oldPassword, password);
+// return ServerAnswer.ok("Password updated");
+// }
+//
+// public void setUserDeserializer(Deserializer userDeserializer) {
+// this.userDeserializer = userDeserializer;
+// }
+//
+// public void setSecurityService(ArgeoSecurityService securityService) {
+// this.securityService = securityService;
+// }
+
+}