New project conventions
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 25 Nov 2014 13:37:07 +0000 (13:37 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 25 Nov 2014 13:37:07 +0000 (13:37 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@7535 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

23 files changed:
org.argeo.security.ldap/.classpath
org.argeo.security.ldap/bnd.bnd [new file with mode: 0644]
org.argeo.security.ldap/build.properties
org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/ArgeoLdapShaPasswordEncoder.java [deleted file]
org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java [deleted file]
org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/ArgeoUserAdminDaoLdap.java [deleted file]
org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java [deleted file]
org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java [deleted file]
org.argeo.security.ldap/src/org/argeo/security/ldap/ArgeoLdapShaPasswordEncoder.java [new file with mode: 0644]
org.argeo.security.ldap/src/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java [new file with mode: 0644]
org.argeo.security.ldap/src/org/argeo/security/ldap/ArgeoUserAdminDaoLdap.java [new file with mode: 0644]
org.argeo.security.ldap/src/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java [new file with mode: 0644]
org.argeo.security.ldap/src/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java [new file with mode: 0644]
org.argeo.security.mvc/.classpath
org.argeo.security.mvc/bnd.bnd [new file with mode: 0644]
org.argeo.security.mvc/build.properties
org.argeo.security.mvc/pom.xml
org.argeo.security.mvc/src/main/java/org/argeo/security/mvc/ArgeoRememberMeServices.java [deleted file]
org.argeo.security.mvc/src/main/java/org/argeo/security/mvc/ArgeoUserInterceptor.java [deleted file]
org.argeo.security.mvc/src/main/java/org/argeo/security/mvc/UsersRolesController.java [deleted file]
org.argeo.security.mvc/src/org/argeo/security/mvc/ArgeoRememberMeServices.java [new file with mode: 0644]
org.argeo.security.mvc/src/org/argeo/security/mvc/ArgeoUserInterceptor.java [new file with mode: 0644]
org.argeo.security.mvc/src/org/argeo/security/mvc/UsersRolesController.java [new file with mode: 0644]

index 5641c7ca39f6826ec4ff5af926bfd34f528f3684..d2953a684d400476bf716ed6a6c3c3ae486fcd0e 100644 (file)
@@ -1,7 +1,9 @@
 <?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>
diff --git a/org.argeo.security.ldap/bnd.bnd b/org.argeo.security.ldap/bnd.bnd
new file mode 100644 (file)
index 0000000..12f35fa
--- /dev/null
@@ -0,0 +1,4 @@
+Import-Package: org.springframework.core,\
+org.springframework.dao,\
+javax.jcr.nodetype,\
+*
\ No newline at end of file
index 5fc538bc83f35ba123c9f84f2eb6124dd65c8789..30f715358d984db427238bb1984c0d19b6ca4004 100644 (file)
@@ -1,4 +1 @@
-source.. = src/main/java/
-output.. = target/classes/
-bin.includes = META-INF/,\
-               .
+source.. = src/
diff --git a/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/ArgeoLdapShaPasswordEncoder.java b/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/ArgeoLdapShaPasswordEncoder.java
deleted file mode 100644 (file)
index ea22ef3..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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;
-       }
-
-}
diff --git a/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java b/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java
deleted file mode 100644 (file)
index 0c7368f..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * 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;
-       }
-
-}
diff --git a/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/ArgeoUserAdminDaoLdap.java b/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/ArgeoUserAdminDaoLdap.java
deleted file mode 100644 (file)
index 37d2a06..0000000
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * 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;
-       }
-}
diff --git a/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java b/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java
deleted file mode 100644 (file)
index 3e9e2cb..0000000
+++ /dev/null
@@ -1,619 +0,0 @@
-/*
- * 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);
-       // }
-       // }
-       //
-       // }
-}
diff --git a/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java b/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java
deleted file mode 100644 (file)
index 87973d9..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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");
-       }
-
-}
diff --git a/org.argeo.security.ldap/src/org/argeo/security/ldap/ArgeoLdapShaPasswordEncoder.java b/org.argeo.security.ldap/src/org/argeo/security/ldap/ArgeoLdapShaPasswordEncoder.java
new file mode 100644 (file)
index 0000000..ea22ef3
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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;
+       }
+
+}
diff --git a/org.argeo.security.ldap/src/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java b/org.argeo.security.ldap/src/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java
new file mode 100644 (file)
index 0000000..0c7368f
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * 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;
+       }
+
+}
diff --git a/org.argeo.security.ldap/src/org/argeo/security/ldap/ArgeoUserAdminDaoLdap.java b/org.argeo.security.ldap/src/org/argeo/security/ldap/ArgeoUserAdminDaoLdap.java
new file mode 100644 (file)
index 0000000..37d2a06
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * 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;
+       }
+}
diff --git a/org.argeo.security.ldap/src/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java b/org.argeo.security.ldap/src/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java
new file mode 100644 (file)
index 0000000..3e9e2cb
--- /dev/null
@@ -0,0 +1,619 @@
+/*
+ * 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);
+       // }
+       // }
+       //
+       // }
+}
diff --git a/org.argeo.security.ldap/src/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java b/org.argeo.security.ldap/src/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java
new file mode 100644 (file)
index 0000000..87973d9
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * 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");
+       }
+
+}
index ff41fbb4dd356738ea37e8d854b1d4a17585bd63..d2953a684d400476bf716ed6a6c3c3ae486fcd0e 100644 (file)
@@ -1,7 +1,9 @@
 <?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>
diff --git a/org.argeo.security.mvc/bnd.bnd b/org.argeo.security.mvc/bnd.bnd
new file mode 100644 (file)
index 0000000..630bdd7
--- /dev/null
@@ -0,0 +1 @@
+Import-Package: javax.servlet,*
index a740a346dcde555da7ae2be700d5928e548e26df..1414da5722785052fb0a803168d190742033d6d8 100644 (file)
@@ -1,2 +1,2 @@
 additional.bundles = org.springframework.beans
-source.. = src/main/java/
+source.. = src/
index dcee8b3221452466818541b2302c2a8444535a02..4fe9ca33652992730ca5c246b2d22077f5843117 100644 (file)
@@ -1,4 +1,6 @@
-<?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>
@@ -8,35 +10,6 @@
        </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>
diff --git a/org.argeo.security.mvc/src/main/java/org/argeo/security/mvc/ArgeoRememberMeServices.java b/org.argeo.security.mvc/src/main/java/org/argeo/security/mvc/ArgeoRememberMeServices.java
deleted file mode 100644 (file)
index fde9f30..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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);
-       }
-
-}
diff --git a/org.argeo.security.mvc/src/main/java/org/argeo/security/mvc/ArgeoUserInterceptor.java b/org.argeo.security.mvc/src/main/java/org/argeo/security/mvc/ArgeoUserInterceptor.java
deleted file mode 100644 (file)
index fd83e9f..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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;
-       }
-
-}
diff --git a/org.argeo.security.mvc/src/main/java/org/argeo/security/mvc/UsersRolesController.java b/org.argeo.security.mvc/src/main/java/org/argeo/security/mvc/UsersRolesController.java
deleted file mode 100644 (file)
index 185d376..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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;
-//     }
-
-}
diff --git a/org.argeo.security.mvc/src/org/argeo/security/mvc/ArgeoRememberMeServices.java b/org.argeo.security.mvc/src/org/argeo/security/mvc/ArgeoRememberMeServices.java
new file mode 100644 (file)
index 0000000..fde9f30
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * 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);
+       }
+
+}
diff --git a/org.argeo.security.mvc/src/org/argeo/security/mvc/ArgeoUserInterceptor.java b/org.argeo.security.mvc/src/org/argeo/security/mvc/ArgeoUserInterceptor.java
new file mode 100644 (file)
index 0000000..fd83e9f
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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;
+       }
+
+}
diff --git a/org.argeo.security.mvc/src/org/argeo/security/mvc/UsersRolesController.java b/org.argeo.security.mvc/src/org/argeo/security/mvc/UsersRolesController.java
new file mode 100644 (file)
index 0000000..185d376
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * 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;
+//     }
+
+}