]> git.argeo.org Git - lgpl/argeo-commons.git/blobdiff - org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java
New project conventions
[lgpl/argeo-commons.git] / org.argeo.security.ldap / src / main / java / org / argeo / security / ldap / jcr / JcrLdapSynchronizer.java
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);
-       // }
-       // }
-       //
-       // }
-}