com.adito.ldap.LdapUserDatabase.java Source code

Java tutorial

Introduction

Here is the source code for com.adito.ldap.LdapUserDatabase.java

Source

/*
*  Adito
*
*  Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU General Public License
*  as published by the Free Software Foundation; either version 2 of
*  the License, or (at your option) any later version.
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public
*  License along with this program; if not, write to the Free Software
*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

package com.adito.ldap;

import com.adito.boot.ContextHolder;
import com.adito.core.CoreEvent;
import com.adito.core.CoreListener;
import com.adito.core.CoreServlet;
import com.adito.properties.Property;
import com.adito.properties.PropertyChangeEvent;
import com.adito.properties.impl.realms.RealmKey;
import com.adito.realms.Realm;
import com.adito.security.DefaultUserDatabase;
import com.adito.security.InvalidLoginCredentialsException;
import com.adito.security.Role;
import com.adito.security.User;
import com.adito.security.UserDatabaseException;
import com.adito.security.UserNotFoundException;
import com.adito.util.ThreadRunner;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.core.AttributesMapper;
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.ldap.core.support.LdapContextSource;
import org.springframework.ldap.core.support.AbstractContextMapper;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.LikeFilter;
import org.springframework.ldap.support.LdapUtils;

import javax.management.relation.RoleNotFoundException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import java.util.*;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateFactory;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;

/**
 * <p/>
 * Ldap implementation of
 * {@link com.adito.security.UserDatabase}.
 */
public class LdapUserDatabase extends DefaultUserDatabase implements CoreListener {

    public static final String COMMON_NAME_ATTRIBUTE = "cn";
    public static final String SURNAME_ATTRIBUTE = "sn";
    public static final String MAIL_ATTRIBUTE = "mail";
    public static final String MEMBER_ATTRIBUTE = "uniqueMember";
    public static final String PASSWORD_ATTRIBUTE = "userPassword";
    public static final String CERTIFICATE_ATTRIBUTE = "userCertificate;binary";
    public static final String UID_ATTRIBUTE = "uid";
    public static final String MODIFY_TIMESTAMP_ATTRIBUTE = "modifyTimestamp";
    public static final String OBJECT_CLASS_ATTRIBUTE = "objectclass";

    public static final String USERS_CLASS = "inetOrgPerson";
    public static final String GROUPS_CLASS = "groupofuniquenames";
    public static final String TOP_CLASS = "top";

    public static final String WILDCARD_SEARCH = "*";

    private static final Log logger = LogFactory.getLog(LdapUserDatabase.class);

    private static final String LDAP_PROTOCOL = "ldap://";
    private static final String LDAP_PROTOCOL_SSL = "ldaps://";

    private UserContainer userContainer = UserContainer.EMPTY_CACHE;
    private GroupContainer groupContainer = GroupContainer.EMPTY_CACHE;
    private ThreadRunner threadRunner;

    private LdapContextSource ldapContextSource;
    private String controllerHost;
    private String serviceAccountName;
    private String serviceAccountPassword;
    private String baseDn;
    private List<String> rdnUsers;
    private List<String> rdnGroups;
    private int userCacheSize;
    private int groupCacheSize;
    private boolean inMemoryCache;
    private int timeToLive;
    private int timeOut;
    private boolean usernamesAreCaseSensitive;
    private boolean followReferrals;
    private boolean useSSL;

    /**
     * Constructeur
     */
    public LdapUserDatabase() {
        this(true, true);
    }

    /**
     * Constructeur
     *
     * @param supportsAccountCreation if the ldap database can be modify, true, else, false
     * @param supportsPasswordChange  if the password can be change, true, else, false
     */
    public LdapUserDatabase(boolean supportsAccountCreation, boolean supportsPasswordChange) {
        super("Ldap", supportsAccountCreation, supportsPasswordChange);
        rdnUsers = new ArrayList<String>();
        rdnGroups = new ArrayList<String>();

    }

    /**
     * @return the container with users
     */
    protected UserContainer getUserContainer() {
        return userContainer;
    }

    /**
     * @return the container with groups
     */
    protected GroupContainer getGroupContainer() {
        return groupContainer;
    }

    /**
     * (non-Javadoc)
     *
     * @see com.adito.security.UserDatabase#logon(java.lang.String,
     *      java.lang.String)
     */
    public User logon(String username, String password)
            throws UserDatabaseException, InvalidLoginCredentialsException {
        LdapUser user;
        try {
            user = getAccount(username);
        } catch (Exception e) {
            logger.error("Failed to logon", e);
            throw new UserDatabaseException("Failed to logon", e);
        }
        assertValidCredentials(user, password);
        return user;
    }

    /**
     * Verify the validity of pair user-password
     *
     * @param user     user that we want verify his password
     * @param password password of the user
     * @throws InvalidLoginCredentialsException
     *          if the password is not valid for this user
     */
    private void assertValidCredentials(LdapUser user, String password) throws InvalidLoginCredentialsException {
        if (!areCredentialsValid(user, password)) {
            throw new InvalidLoginCredentialsException("Invalid username or password.");
        }
    }

    /**
     * Return if pair user - password is valid or not
     *
     * @param user     user that we want verify his password
     * @param password password of the user
     * @return true if password is valid for this user, false in other case
     */
    protected final boolean areCredentialsValid(LdapUser user, String password) {
        DirContext ctx = null;
        try {
            ctx = ldapContextSource.getContext(user.getDn(), password);
            return true;
        } catch (Exception e) {
            // Context creation failed - authentication did not succeed
            logger.error("Login failed", e);
            return false;
        } finally {

            LdapUtils.closeContext(ctx);
        }
    }

    /**
     * @return all users in the ldap directory
     */
    @SuppressWarnings("unchecked")
    public Iterable<User> allUsers() {
        Iterator<? extends User> retrievePrincipals = userContainer.retrievePrincipals();
        return (Iterable<User>) toIterable(retrievePrincipals);
    }

    /**
     * (non-Javadoc)
     *
     * @see com.adito.security.UserDatabase#getAccount(java.lang.String)
     */

    public LdapUser getAccount(String username) throws Exception {

        if (logger.isDebugEnabled()) {
            logger.debug("Getting account " + username);
        }
        if (!userContainer.containsPrincipal(username)) {
            loadUsers(username, false);
        }
        if (!userContainer.containsPrincipal(username)) {
            throw new UserNotFoundException(username + " is not a valid user!");
        }

        return userContainer.retrievePrincipal(username);

    }

    /**
     * (non-Javadoc)
     *
     * @see com.adito.security.UserDatabase#getRole(java.lang.String)
     */
    public Role getRole(String groupName) throws UserDatabaseException, RoleNotFoundException {
        if (logger.isDebugEnabled()) {
            logger.debug("Getting group " + groupName);
        }

        if (!groupContainer.containsPrincipal(groupName)) {
            loadRoles(groupName, false);

        }
        if (!groupContainer.containsPrincipal(groupName)) {
            throw new RoleNotFoundException(groupName + " is not a valid group!");
        }
        return groupContainer.retrievePrincipal(groupName);
    }

    /**
     * @return all the roles(groups) in ldap directory
     */
    @SuppressWarnings("unchecked")
    public Iterable<Role> allRoles() {
        Iterator<? extends Role> retrievePrincipals = groupContainer.retrievePrincipals();
        return (Iterable<Role>) toIterable(retrievePrincipals);
    }

    /**
     * (non-Javadoc)
     *
     * @see com.adito.security.DefaultUserDatabase#createRole(java.lang.String)
     */
    public Role createRole(String rolename) throws Exception {
        if (!supportsAccountCreation()) {
            throw new UnsupportedOperationException("User database is read-only");
        }
        if (logger.isInfoEnabled()) {
            logger.info("create role " + rolename);
        }
        LdapTemplate ldapTemplate = new LdapTemplate();
        ldapTemplate.setContextSource(ldapContextSource);

        String rdn = COMMON_NAME_ATTRIBUTE + "=" + rolename + "," + rdnGroups.get(0);
        DirContextAdapter context = new DirContextAdapter(rdn);
        context.setUpdateMode(false);
        context.setAttributeValues(OBJECT_CLASS_ATTRIBUTE, new String[] { TOP_CLASS, GROUPS_CLASS });
        ldapTemplate.bind(context);

        String originalDn = rdn + "," + baseDn;
        LdapGroup g = new LdapGroup(rolename, originalDn, getRealm());
        groupContainer.storeGroup(g);
        return g;

    }

    /**
     * (non-Javadoc)
     *
     * @see com.adito.security.DefaultUserDatabase#deleteRole(java.lang.String)
     */
    public void deleteRole(String rolename) throws Exception {
        if (!supportsAccountCreation()) {
            throw new UnsupportedOperationException("User database is read-only");
        }

        LdapTemplate ldapTemplate = new LdapTemplate();
        ldapTemplate.setContextSource(ldapContextSource);

        String dn = ((LdapGroup) getRole(rolename)).getDn();

        int ind = dn.indexOf(baseDn);

        String rdn = dn.substring(0, ind - 1);

        //take the name of user'member of this group
        NamingEnumeration e = (NamingEnumeration) ldapTemplate.lookup(rdn, new AttributesMapper() {
            public Object mapFromAttributes(Attributes attrs) throws NamingException {

                return attrs.get(MEMBER_ATTRIBUTE).getAll();

            }
        });

        //delete the group in database
        ldapTemplate.unbind(rdn);
        groupContainer.removeGroup((LdapGroup) getRole(rolename));

        // delete the group in Ldapuser
        for (; e.hasMore();) {
            LdapUser u = getAccountFromDN(e.next().toString());
            u.setRoles(getGroupsForUser(u.getDn()));
            userContainer.storePrincipal(u);
        }

    }

    /**
     * (non-Javadoc)
     *
     * @see com.adito.security.UserDatabase#checkPassword(java.lang.String,java.lang.String)
     */
    public boolean checkPassword(String username, String password)
            throws UserDatabaseException, InvalidLoginCredentialsException {
        try {
            LdapUser user = getAccount(username);
            return areCredentialsValid(user, password);
        } catch (UserNotFoundException e) {
            throw new UserDatabaseException("Failed to check password", e);
        } catch (Exception e) {
            throw new UserDatabaseException("Failed to check password", e);
        }
    }

    /**
     * (non-Javadoc)
     *
     * @see com.adito.security.DefaultUserDatabase#changePassword(java.lang.String, java.lang.String, java.lang.String, boolean)
     */
    public void changePassword(String username, String oldPassword, String password,
            boolean forcePasswordChangeAtLogon) throws UserDatabaseException, InvalidLoginCredentialsException {
        if (!supportsPasswordChange()) {
            throw new InvalidLoginCredentialsException("Database doesn't support password change.");
        }

        DirContext ctx = null;
        try {
            if (forcePasswordChangeAtLogon)
                getAccount(username).setLastPasswordChange(null);
            else
                getAccount(username).setLastPasswordChange(new Date());

            String dn = getAccount(username).getDn();
            int ind = dn.indexOf(baseDn);
            String rdn = dn.substring(0, ind - 1);
            ctx = ldapContextSource.getContext(dn, oldPassword);
            LdapTemplate ldapTemplate = new LdapTemplate();
            ldapTemplate.setContextSource(ldapContextSource);
            DirContextOperations context = ldapTemplate.lookupContext(rdn);
            context.setAttributeValue(PASSWORD_ATTRIBUTE, password);
            ldapTemplate.modifyAttributes(context);
        } catch (Exception e) {
            // Context creation failed - authentication did not succeed
            logger.error("Login failed", e);

        } finally {
            LdapUtils.closeContext(ctx);
        }
    }

    /**
     * (non-Javadoc)
     *
     * @see com.adito.security.DefaultUserDatabase#setPassword(java.lang.String, java.lang.String, boolean, com.adito.security.User, java.lang.String)
     */
    public void setPassword(String username, String password, boolean forcePasswordChangeAtLogon, User adminUser,
            String adminPassword) throws UserDatabaseException, InvalidLoginCredentialsException {
        if (!supportsPasswordChange()) {
            throw new InvalidLoginCredentialsException("Database doesn't support password change.");
        }

        LdapUser user;

        try {
            user = getAccount(username);
        } catch (Exception e) {
            throw new UserDatabaseException(e.toString());
        }

        if (forcePasswordChangeAtLogon)
            user.setLastPasswordChange(null);
        else
            user.setLastPasswordChange(new Date());

        LdapTemplate ldapTemplate = new LdapTemplate();
        ldapTemplate.setContextSource(ldapContextSource);
        Attribute attr = new BasicAttribute(PASSWORD_ATTRIBUTE, password);
        ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr);
        try {
            String dn = getAccount(username).getDn();
            int ind = dn.indexOf(baseDn);
            String rdn = dn.substring(0, ind - 1);
            ldapTemplate.modifyAttributes(rdn, new ModificationItem[] { item });
        } catch (Exception e) {
            throw new UserDatabaseException("Error in LDAP server");
        }

    }

    /**
     * (non-Javadoc)
     *
     * @see com.adito.security.DefaultUserDatabase#createAccount(java.lang.String, java.lang.String, java.lang.String, java.lang.String, com.adito.security.Role[])
     */
    public User createAccount(String username, String password, String email, String fullname, Role[] roles)
            throws Exception {
        if (!supportsAccountCreation()) {
            throw new UnsupportedOperationException("User database is read-only");
        }
        LdapTemplate ldapTemplate = new LdapTemplate();
        ldapTemplate.setContextSource(ldapContextSource);
        DirContextAdapter context = new DirContextAdapter(UID_ATTRIBUTE + "=" + username + "," + rdnUsers.get(0));
        context.setUpdateMode(false);
        context.setAttributeValues(OBJECT_CLASS_ATTRIBUTE, new String[] { TOP_CLASS, USERS_CLASS });
        context.setAttributeValue(COMMON_NAME_ATTRIBUTE, fullname);
        context.setAttributeValue(SURNAME_ATTRIBUTE, fullname);
        context.setAttributeValue(PASSWORD_ATTRIBUTE, password);
        context.setAttributeValue(MAIL_ATTRIBUTE, email);
        context.setAttributeValue(UID_ATTRIBUTE, username);
        ldapTemplate.bind(context);

        String originalDn = UID_ATTRIBUTE + "=" + username + "," + rdnUsers.get(0) + "," + baseDn;

        for (Role role : roles) {
            LdapGroup group = (LdapGroup) role;
            String dnGroup = group.getDn();
            int ind = dnGroup.indexOf(baseDn);
            String rdnGroup = dnGroup.substring(0, ind - 1);
            DirContextOperations context2 = ldapTemplate.lookupContext(rdnGroup);
            context2.addAttributeValue(MEMBER_ATTRIBUTE, originalDn);
            ldapTemplate.modifyAttributes(context2);
        }

        LdapUser u = new LdapUser(username, originalDn, email, fullname, new Date(), getRealm());
        u.setRoles(roles);
        userContainer.storePrincipal(u);
        return u;

    }

    /**
     * (non-Javadoc)
     *
     * @see com.adito.security.UserDatabase#updateAccount(com.adito.security.User, java.lang.String, java.lang.String, com.adito.security.Role[])
     */
    public void updateAccount(User user, String email, String fullname, Role[] roles) throws Exception {
        if (!supportsAccountCreation()) {
            throw new UnsupportedOperationException("User database is read-only");
        }

        if (logger.isInfoEnabled()) {
            logger.info("update Account " + user.getPrincipalName());
        }

        LdapTemplate ldapTemplate = new LdapTemplate();
        ldapTemplate.setContextSource(ldapContextSource);

        String originalDn = ((LdapUser) user).getDn();
        int ind = originalDn.indexOf(baseDn);
        String rdn = originalDn.substring(0, ind - 1);

        //update email and fullname
        DirContextOperations context = ldapTemplate.lookupContext(rdn);
        context.setAttributeValue(MAIL_ATTRIBUTE, email);
        context.setAttributeValue(COMMON_NAME_ATTRIBUTE, fullname);
        ldapTemplate.modifyAttributes(context);

        //update roles

        Role[] rolesOld = user.getRoles();

        //String originalDn = rdn + "," + baseDn;

        /* delete old roles*/
        for (Role aRolesOld : rolesOld) {
            LdapGroup group = (LdapGroup) aRolesOld;
            String dn = group.getDn();
            int ind1 = dn.indexOf(baseDn);
            String rdn1 = dn.substring(0, ind1 - 1);
            logger.debug("delete in:  " + rdn1);
            DirContextOperations context2 = ldapTemplate.lookupContext(rdn1);
            context2.removeAttributeValue(MEMBER_ATTRIBUTE, originalDn);
            ldapTemplate.modifyAttributes(context2);
            logger.debug("attribut remove: " + originalDn);

        }

        /* add new roles */
        int i = 0;
        while (i < roles.length) {
            LdapGroup group = (LdapGroup) roles[i];
            String dn = group.getDn();
            int ind1 = dn.indexOf(baseDn);
            String rdn1 = dn.substring(0, ind1 - 1);

            logger.debug("add: " + rdn1);
            DirContextOperations context3 = ldapTemplate.lookupContext(rdn1);
            context3.addAttributeValue(MEMBER_ATTRIBUTE, originalDn);
            ldapTemplate.modifyAttributes(context3);
            i++;
        }

        loadUsers(user.getPrincipalName(), false);
    }

    /**
     * (non-Javadoc)
     *
     * @see com.adito.security.DefaultUserDatabase#deleteAccount(com.adito.security.User)
     */
    protected void performDeleteAccount(User user) throws Exception, UserNotFoundException {

        LdapTemplate ldapTemplate = new LdapTemplate();
        ldapTemplate.setContextSource(ldapContextSource);

        //delete the name of user in groups
        Role[] roles = user.getRoles();

        String dn = ((LdapUser) user).getDn();
        int ind = dn.indexOf(baseDn);
        String rdn = dn.substring(0, ind - 1);

        for (Role role : roles) {
            LdapGroup group = (LdapGroup) role;
            String dnGroup = group.getDn();
            int ind1 = dnGroup.indexOf(baseDn);
            String rdnGroup = dnGroup.substring(0, ind1 - 1);
            DirContextOperations context = ldapTemplate.lookupContext(rdnGroup);
            context.removeAttributeValue(MEMBER_ATTRIBUTE, dn);
            ldapTemplate.modifyAttributes(context);
        }

        //delete the user in database
        ldapTemplate.unbind(rdn);

        userContainer.removePrincipal(user.getPrincipalName());

    }

    /**
     * Load users in container
     *
     * @param filter               name of users that load in container
     * @param removeMissingEntries true, it's delete all user is in before loading, false, it's delete nothing
     */
    private void loadUsers(final String filter, final boolean removeMissingEntries) {

        if (logger.isInfoEnabled()) {
            logger.info("load users of name like " + filter);
        }

        final Collection<String> usernames = userContainer.retrievePrincipalNames();
        LdapTemplate ldapTemplate = new LdapTemplate();
        ldapTemplate.setContextSource(ldapContextSource);

        AndFilter filterS = new AndFilter();
        filterS.and(new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, USERS_CLASS));
        filterS.and(new LikeFilter(UID_ATTRIBUTE, filter));
        for (String rdn : rdnUsers) {
            List users = ldapTemplate.search(rdn, filterS.encode(), new AbstractContextMapper() {
                protected Object doMapFromContext(DirContextOperations ctx) {

                    if (ctx.getStringAttribute(MAIL_ATTRIBUTE) == null)
                        ctx.setAttributeValue(MAIL_ATTRIBUTE, "");
                    if (ctx.getStringAttribute(COMMON_NAME_ATTRIBUTE) == null)
                        ctx.setAttributeValue(COMMON_NAME_ATTRIBUTE, "");

                    String uid = ctx.getStringAttribute(UID_ATTRIBUTE);

                    //get the date of last modify of this user
                    LdapTemplate tmp = new LdapTemplate();
                    tmp.setContextSource(ldapContextSource);
                    final String[] attrOp = { MODIFY_TIMESTAMP_ATTRIBUTE };
                    Object o = tmp.lookup(ctx.getDn(), attrOp, new ContextMapper() {
                        public Object mapFromContext(Object ctx) {
                            DirContextAdapter adapter = (DirContextAdapter) ctx;
                            return adapter.getStringAttribute(attrOp[0]);
                        }
                    }

                    );

                    Date lastPasswordChange; //the time of last change for the entry
                    if (o != null) {
                        String modifyTimestamp = o.toString();
                        lastPasswordChange = getDate(modifyTimestamp);
                    } else {
                        //if the modifyTimeStamp is null
                        lastPasswordChange = new Date();
                    }

                    return new LdapUser(uid, ctx.getNameInNamespace(), ctx.getStringAttribute(MAIL_ATTRIBUTE),
                            ctx.getStringAttribute(COMMON_NAME_ATTRIBUTE), lastPasswordChange, getRealm());

                }
            });

            if (removeMissingEntries) {
                userContainer.updateRemovedPrincipals(usernames);
            }

            for (Object user : users) {
                LdapUser u = (LdapUser) user;
                u.setRoles(getGroupsForUser(u.getDn()));
                userContainer.storePrincipal(u);
            }
        }

    }

    /**
     * Load groups in container
     *
     * @param filter               name of groups that load in container
     * @param removeMissingEntries true, it's delete all groups is in before loading, false, it's delete nothing
     */
    private void loadRoles(final String filter, final boolean removeMissingEntries) {

        if (logger.isInfoEnabled()) {
            logger.info("load groups of name like " + filter);
        }

        final Collection<String> groupNames = groupContainer.retrievePrincipalNames();

        LdapTemplate ldapTemplate = new LdapTemplate();
        ldapTemplate.setContextSource(ldapContextSource);

        AndFilter filterS = new AndFilter();
        filterS.and(new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, GROUPS_CLASS));
        filterS.and(new LikeFilter(COMMON_NAME_ATTRIBUTE, filter));
        for (String rdn : rdnGroups) {
            List groups = ldapTemplate.search(rdn, filterS.encode(), new AbstractContextMapper() {
                protected Object doMapFromContext(DirContextOperations ctx) {
                    String cn = ctx.getStringAttribute(COMMON_NAME_ATTRIBUTE);
                    return new LdapGroup(cn, ctx.getNameInNamespace(), getRealm());
                }
            });

            if (removeMissingEntries) {
                groupContainer.updateRemovedGroups(groupNames);
            }

            for (final Object group : groups) {
                groupContainer.storeGroup((LdapGroup) group);
            }
        }

    }

    /**
     * Get a user account given a DN. <code>null</code> will be returned if no
     * such account can be found.
     *
     * @param dn dn
     * @return user account
     * @throws UserDatabaseException on any error
     */

    public LdapUser getAccountFromDN(final String dn) throws UserDatabaseException {

        if (dn.endsWith(baseDn)) {
            // This looks like a DN so do a lookup
            if (logger.isDebugEnabled()) {
                logger.debug("Looking up account using DN: " + dn);
            }
            LdapTemplate ldapTemplate = new LdapTemplate();
            ldapTemplate.setContextSource(ldapContextSource);

            int indx = dn.indexOf("," + baseDn);
            final String rdn = dn.substring(0, indx);
            Object user = ldapTemplate.lookup(rdn, new AbstractContextMapper() {
                protected Object doMapFromContext(DirContextOperations ctx) {

                    if (ctx.getStringAttribute(MAIL_ATTRIBUTE) == null)
                        ctx.setAttributeValue(MAIL_ATTRIBUTE, "");
                    if (ctx.getStringAttribute(COMMON_NAME_ATTRIBUTE) == null)
                        ctx.setAttributeValue(COMMON_NAME_ATTRIBUTE, "");

                    String uid = ctx.getStringAttribute(UID_ATTRIBUTE);

                    //get the date of last modify of this user
                    LdapTemplate tmp = new LdapTemplate();
                    tmp.setContextSource(ldapContextSource);
                    final String[] attrOp = { MODIFY_TIMESTAMP_ATTRIBUTE };
                    Object o = tmp.lookup(ctx.getDn(), attrOp, new ContextMapper() {
                        public Object mapFromContext(Object ctx) {
                            DirContextAdapter adapter = (DirContextAdapter) ctx;
                            return adapter.getStringAttribute(attrOp[0]);
                        }
                    }

                    );

                    Date lastPasswordChange; //the time of last change for the entry
                    if (o != null) {
                        String modifyTimestamp = o.toString();
                        lastPasswordChange = getDate(modifyTimestamp);
                    } else {
                        //if the modifyTimeStamp is null
                        lastPasswordChange = new Date();
                    }

                    return new LdapUser(uid, ctx.getNameInNamespace(), ctx.getStringAttribute(MAIL_ATTRIBUTE),
                            ctx.getStringAttribute(COMMON_NAME_ATTRIBUTE), lastPasswordChange, getRealm());

                }
            });

            if (user == null) {
                throw new UserDatabaseException("User not found for DN " + dn);
            }
            return (LdapUser) user;

        }
        throw new UserDatabaseException("Certificate requires subject to be DN of Ldap user");
    }

    /**
     * Return all the roles(groupes) for one user
     *
     * @param dn distinguished name of the user
     * @return array of user's role
     */
    private Role[] getGroupsForUser(final String dn) {
        LdapTemplate ldapTemplate = new LdapTemplate();
        ldapTemplate.setContextSource(ldapContextSource);

        AndFilter filterS = new AndFilter();
        filterS.and(new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, GROUPS_CLASS));
        filterS.and(new EqualsFilter(MEMBER_ATTRIBUTE, dn));
        List allGroups = new ArrayList();
        for (String rdn : rdnGroups) {
            List groups = ldapTemplate.search(rdn, filterS.encode(), new AttributesMapper() {
                public Object mapFromAttributes(Attributes attrs) throws NamingException {
                    try {
                        return getRole(attrs.get(COMMON_NAME_ATTRIBUTE).get().toString());
                    } catch (UserDatabaseException e) {
                        logger.error("UserDatabaseException :" + e);
                        return null;
                    } catch (RoleNotFoundException e) {
                        logger.error("RoleNotFoundException :" + e);
                        return null;
                    }
                }
            });

            for (Object o : groups) {
                allGroups.add(o);
            }
        }

        return (LdapGroup[]) allGroups.toArray(new LdapGroup[allGroups.size()]);

    }

    /**
     * Register a certificate X509 in LDAP server for a user
     * @param user the owner of certificate
     * @param x509Certificate the certificate to register
     * @throws Exception
     */
    public void registerCertificate(User user, X509Certificate x509Certificate) throws Exception {

        if (!supportsAccountCreation()) {
            throw new UnsupportedOperationException("User database is read-only");
        }

        if (logger.isInfoEnabled()) {
            logger.info("Register Certificat " + user.getPrincipalName());
        }

        if (user instanceof LdapUser) {

            LdapTemplate ldapTemplate = new LdapTemplate();
            ldapTemplate.setContextSource(ldapContextSource);

            LdapUser ldapUser = (LdapUser) user;

            String dn = ldapUser.getDn();

            int ind = dn.indexOf(baseDn);

            String rdn = dn.substring(0, ind - 1);

            //add (or update) the certificat of user
            DirContextOperations context = ldapTemplate.lookupContext(rdn);
            context.setAttributeValue(CERTIFICATE_ATTRIBUTE, x509Certificate.getEncoded());
            ldapTemplate.modifyAttributes(context);
        }

    }

    /**
     * Return the certificat store in LDAP server for a user (if exist)
     * @param user
     * @return X509Certificate of the user or null id this certificate doesn't exist
     */
    public X509Certificate getCertificate(User user) {

        if (logger.isInfoEnabled()) {
            logger.info("Get Certificat for " + user.getPrincipalName());
        }

        LdapTemplate ldapTemplate = new LdapTemplate();
        ldapTemplate.setContextSource(ldapContextSource);

        AndFilter filterS = new AndFilter();
        String dn = ((LdapUser) user).getDn();
        int ind = dn.indexOf(baseDn);
        String rdn = dn.substring(0, ind - 1);
        filterS.and(new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, USERS_CLASS));
        filterS.and(new LikeFilter(UID_ATTRIBUTE, user.getPrincipalName()));
        List certificats = ldapTemplate.search(rdn, filterS.encode(), new AttributesMapper() {
            public Object mapFromAttributes(Attributes attrs) throws NamingException {

                try {
                    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

                    return certificateFactory.generateCertificate(
                            new ByteArrayInputStream((byte[]) attrs.get(CERTIFICATE_ATTRIBUTE).get()));

                } catch (Exception e) {

                    throw new NamingException(e.toString());
                }

            }
        });

        if (certificats.size() == 0) {
            return null;
        } else {
            X509Certificate cert = (X509Certificate) certificats.get(0);
            return cert;
        }

    }

    /**
     * Return the user associated to a certiciate in LDAP server
     * @param x509Certificate
     * @return the user of the certificate or null if no associate exist
     */
    public User getUserByCertificate(X509Certificate x509Certificate) {

        if (logger.isInfoEnabled()) {
            logger.info("Get user for serial number " + x509Certificate.getSerialNumber().toString(16));
        }

        LdapTemplate ldapTemplate = new LdapTemplate();
        ldapTemplate.setContextSource(ldapContextSource);

        AndFilter filterS = new AndFilter();
        filterS.and(new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, USERS_CLASS));
        filterS.and(new LikeFilter("userCertificate", "*"));
        for (String rdn : rdnUsers) {
            List serialNumbers = ldapTemplate.search(rdn, filterS.encode(), new AbstractContextMapper() {
                public Object doMapFromContext(DirContextOperations ctx) {

                    try {
                        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

                        X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(
                                new ByteArrayInputStream((byte[]) ctx.getObjectAttribute(CERTIFICATE_ATTRIBUTE)));

                        if (ctx.getStringAttribute(MAIL_ATTRIBUTE) == null)
                            ctx.setAttributeValue(MAIL_ATTRIBUTE, "");
                        if (ctx.getStringAttribute(COMMON_NAME_ATTRIBUTE) == null)
                            ctx.setAttributeValue(COMMON_NAME_ATTRIBUTE, "");

                        String uid = ctx.getStringAttribute(UID_ATTRIBUTE);

                        //get the date of last modify of this user
                        LdapTemplate tmp = new LdapTemplate();
                        tmp.setContextSource(ldapContextSource);
                        final String[] attrOp = { MODIFY_TIMESTAMP_ATTRIBUTE };
                        Object o = tmp.lookup(ctx.getDn(), attrOp, new ContextMapper() {
                            public Object mapFromContext(Object ctx) {
                                DirContextAdapter adapter = (DirContextAdapter) ctx;
                                return adapter.getStringAttribute(attrOp[0]);

                            }
                        }

                        );

                        Date lastPasswordChange; //the time of last change for the entry
                        if (o != null) {
                            String modifyTimestamp = o.toString();
                            lastPasswordChange = getDate(modifyTimestamp);
                        } else {
                            //if the modifyTimeStamp is null
                            lastPasswordChange = new Date();
                        }

                        LdapUser user = new LdapUser(uid, ctx.getNameInNamespace(),
                                ctx.getStringAttribute(MAIL_ATTRIBUTE),
                                ctx.getStringAttribute(COMMON_NAME_ATTRIBUTE), lastPasswordChange, getRealm());

                        Object[] tab = { x509Certificate.getSerialNumber(), user };

                        return tab;

                    } catch (Exception e) {

                        logger.error(e);
                        return null;
                    }
                }
            });

            if (serialNumbers != null) {
                for (Object o : serialNumbers) {

                    Object[] tab = (Object[]) o;
                    BigInteger serialNumber = (BigInteger) tab[0];
                    LdapUser user = (LdapUser) tab[1];
                    if (serialNumber.equals(x509Certificate.getSerialNumber()))
                        return user;
                }
            }
        }

        return null;

    }

    /*
    * (non-Javadoc)
    *
    * @see com.adito.core.CoreListener#coreEvent(com.adito.core.CoreEvent)
    */
    public void coreEvent(CoreEvent event) {
        // When in install mode, the wizard looks after re-initialising the user
        // database
        if (!ContextHolder.getContext().isSetupMode() && event instanceof PropertyChangeEvent) {
            PropertyChangeEvent changeEvent = (PropertyChangeEvent) event;
            if (changeEvent.getDefinition().getName().startsWith("ldap.")) {
                if (logger.isInfoEnabled()) {
                    logger.info("Ldap configuration changed. Re-initialising");
                }
                try {
                    //initialise();
                } catch (Exception e) {
                    logger.error("Failed to re-initialise Ldap.", e);
                }
            }
        }
    }

    /**
     * (non-Javadoc)
     *
     * @see com.adito.security.UserDatabase#open(com.adito.core.CoreServlet, com.adito.realms.Realm)
     */
    public void open(CoreServlet controllingServlet, Realm realm) throws Exception {
        try {
            super.open(controllingServlet, realm);

            if (logger.isInfoEnabled()) {
                logger.info("User database is being opened...");
            }

            initConfiguration();

            userContainer = new UserContainer(userCacheSize, inMemoryCache, usernamesAreCaseSensitive, baseDn);
            groupContainer = new GroupContainer(groupCacheSize, inMemoryCache);

            ldapContextSource = new LdapContextSource();
            String ldapProtocol = LDAP_PROTOCOL;
            if (useSSL) {
                ldapProtocol = LDAP_PROTOCOL_SSL;
            } else {
                ldapProtocol = LDAP_PROTOCOL;
            }
            ldapContextSource.setUrl(ldapProtocol + controllerHost);

            ldapContextSource.setBase(baseDn);
            ldapContextSource.setUserDn(serviceAccountName);
            ldapContextSource.setPassword(serviceAccountPassword);
            ldapContextSource.afterPropertiesSet();

            Map baseEnvProps = new Hashtable();
            baseEnvProps.put("com.sun.jndi.ldap.connect.timeout", timeOut);

            if (followReferrals) {
                baseEnvProps.put(Context.REFERRAL, "follow");
            }

            ldapContextSource.setBaseEnvironmentProperties(baseEnvProps);

            CoreServlet.getServlet().addCoreListener(this);
            threadRunner = new ThreadRunner("CacheUpdater", getCacheUpdaterJob(), timeToLive);
            threadRunner.start();
        } catch (Exception e) {
            close();
            throw e;
        }
    }

    /**
     * Set all properties (option and connection)
     *
     * @throws Exception if one of propertie are not valide
     */
    private void initConfiguration() throws Exception {
        Properties propertyNames = new Properties();

        setControllerHost(getProperty("ldap.controllerHost", propertyNames));
        setBaseDN(getProperty("ldap.baseDN", propertyNames));
        setServiceAccountName(getProperty("ldap.serviceAccountUsername", propertyNames));
        setServiceAccountPassword(getProperty("ldap.serviceAccountPassword", propertyNames));
        setDomainUsers(getProperty("ldap.domainUsers1", propertyNames));
        setDomainUsers(getProperty("ldap.domainUsers2", propertyNames));
        setDomainUsers(getProperty("ldap.domainUsers3", propertyNames));
        setDomainGroups(getProperty("ldap.domainGroups1", propertyNames));
        setDomainGroups(getProperty("ldap.domainGroups2", propertyNames));
        setDomainGroups(getProperty("ldap.domainGroups3", propertyNames));

        setUseSSL(getPropertyBoolean("ldap.useSSL", propertyNames));
        setFollowReferrals(getPropertyBoolean("ldap.followReferrals", propertyNames));
        setUserCacheSize(getPropertyInt("ldap.cacheUserMaxObjects", propertyNames));
        setGroupCacheSize(getPropertyInt("ldap.cacheGroupMaxObjects", propertyNames));
        setInMemoryCache(getPropertyBoolean("ldap.cacheInMemory", propertyNames));
        setTimeToLive(getPropertyInt("ldap.userCacheTTL", propertyNames));

        setUsernamesAreCaseSensitive(getPropertyBoolean("ldap.usernamesAreCaseSensitive", propertyNames));
        setTimeOut(getPropertyInt("ldap.connection.timeout", propertyNames));
    }

    /**
     * Return millisecond
     *
     * @param minutes number of minut
     * @return millesecond equivalent to minutes
     */
    private static int minutesToMillis(int minutes) {
        return minutes * 60 * 1000;
    }

    /**
     * upadate caches
     *
     * @return proccess
     */
    private Runnable getCacheUpdaterJob() {
        return new Runnable() {
            public void run() {
                if (logger.isInfoEnabled()) {
                    logger.info("Caching items");
                    logger.info("Caching roles");
                }

                loadRoles(WILDCARD_SEARCH, true);

                if (logger.isInfoEnabled()) {
                    logger.info("Finished caching roles");
                    logger.info("Caching users");
                }

                // once we have the roles, we'll be able to find the users role
                // assignments

                loadUsers(WILDCARD_SEARCH, true);

                if (logger.isInfoEnabled()) {
                    logger.info("Finished caching users");
                    logger.info("Finished caching items");
                }
            }
        };
    }

    /**
     * (non-Javadoc)
     *
     * @see com.adito.core.Database#close()
     */
    public void close() throws Exception {
        super.close();
        CoreServlet.getServlet().removeCoreListener(this);
        if (threadRunner != null) {
            threadRunner.stop();
        }
        userContainer.close();
        groupContainer.close();
    }

    /**
     * (non-Javadoc)
     *
     * @see com.adito.security.UserDatabase#logout(com.adito.security.User)
     */
    public void logout(User user) {
        //nothing special to do
    }

    /**
     * As some characters are escaped via a backslash, we need to add some more
     * slashes to get this to work.
     *
     * @param dn
     * @return String
     */
    private static String getEscapedDn(String dn) {
        String escapeDn = dn.replaceAll("\\\\", "\\\\\\\\");
        String escapeForwardSlash = escapeDn.replaceAll("/", "\\\\/");
        return escapeForwardSlash;
    }

    /** This method return the date of a timestamp of ldap
     2008123115959Z=2008,12(december),31,11:59:59
     */
    /**
     * Return a date equivalent to a timestamp ldap
     * In the case of timestamp is 2008123115959Z, the date is 2008,12(december),31,11:59:59
     *
     * @param timestamp string that reprsent the timestamp like 2008123115959Z
     * @return a date of timestamp
     */
    private static Date getDate(String timestamp) {

        GregorianCalendar calendar = new GregorianCalendar(Integer.parseInt(timestamp.substring(0, 4)),
                Integer.parseInt(timestamp.substring(4, 6)) - 1, Integer.parseInt(timestamp.substring(6, 8)),
                Integer.parseInt(timestamp.substring(8, 10)), Integer.parseInt(timestamp.substring(10, 12)),
                Integer.parseInt(timestamp.substring(12, 14)));

        return calendar.getTime();

    }

    /**
     * Set controllerHost
     *
     * @param controllerHost name of controllerHost
     * @throw Exception if name of controllerHost is empty
     */
    private void setControllerHost(String controllerHost) throws Exception {
        if (controllerHost.equals("")) {
            throw new IllegalArgumentException("No ldap controller host configured.");
        }
        this.controllerHost = controllerHost;
    }

    /**
     * Set the base DN (example : dc=adito, dc=com)
     *
     * @param baseDN name of domain
     * @throws Exception if name of domain is empty
     */
    private void setBaseDN(String baseDN) throws Exception {
        this.baseDn = baseDN.trim();
        if (this.baseDn.equals("")) {
            throw new IllegalArgumentException("No ldap base DN configured.");
        }
    }

    /**
     * Set rdnUsers
     *
     * @param rdnUsers distinguish name of users
     * @throws Exception if distinguish name is empty
     */
    private void setDomainUsers(String rdnUsers) throws Exception {
        if (!rdnUsers.trim().equals(""))
            this.rdnUsers.add(rdnUsers.trim());
        if (this.rdnUsers.size() == 1 && this.rdnUsers.equals("")) {
            throw new IllegalArgumentException("No ldap domain of user configured.");
        }
    }

    /**
     * Set rdnGroups
     *
     * @param rdnGroups distinguish name of groups
     * @throws Exception if distinguish name is empty
     */
    private void setDomainGroups(String rdnGroups) throws Exception {
        if (!this.rdnGroups.equals(""))
            this.rdnGroups.add(rdnGroups.trim());
        if (this.rdnGroups.size() == 1 && this.rdnGroups.equals("")) {
            throw new IllegalArgumentException("No ldap domain of group configured.");
        }
    }

    /**
     * Set serviceAccountName
     *
     * @param serviceAccountName
     */
    void setServiceAccountName(String serviceAccountName) {
        this.serviceAccountName = serviceAccountName == null ? null : serviceAccountName.trim();
    }

    /**
     * Set serviceAccountPassword
     *
     * @param serviceAccountPassword
     */
    void setServiceAccountPassword(String serviceAccountPassword) {
        this.serviceAccountPassword = serviceAccountPassword;
    }

    /**
     * Set folloRefferals
     *
     * @param followReferrals
     */
    void setFollowReferrals(boolean followReferrals) {
        this.followReferrals = followReferrals;
    }

    /**
     * Set useSSL
     *
     * @param useSSL
     */
    void setUseSSL(boolean useSSL) {
        this.useSSL = useSSL;
    }

    /**
     * Set userCacheSize
     *
     * @param userCacheSize
     */
    void setUserCacheSize(int userCacheSize) {
        this.userCacheSize = userCacheSize;
    }

    /**
     * Set groupCacheSize
     *
     * @param groupCacheSize
     */
    void setGroupCacheSize(int groupCacheSize) {
        this.groupCacheSize = groupCacheSize;
    }

    /**
     * Set inMemoryCache
     *
     * @param inMemoryCache
     */
    void setInMemoryCache(boolean inMemoryCache) {
        this.inMemoryCache = inMemoryCache;
    }

    /**
     * Set timeToLive and modify it if it too small
     *
     * @param timeToLive
     */
    void setTimeToLive(int timeToLive) {
        if (timeToLive < 1) {
            logger.warn(
                    "Cache TTL is less than 1 minute. This would cause serious performance problems. The minimum value of 1 minute will now be used");
            timeToLive = 1;
        }
        this.timeToLive = minutesToMillis(timeToLive);
    }

    /**
     * Set usernameAreCaseSensitive
     *
     * @param usernamesAreCaseSensitive
     */
    private void setUsernamesAreCaseSensitive(boolean usernamesAreCaseSensitive) {
        this.usernamesAreCaseSensitive = usernamesAreCaseSensitive;
    }

    /**
     * Set timeOut
     *
     * @param timeOut
     */
    private void setTimeOut(int timeOut) {
        this.timeOut = timeOut * 1000;
    }

    /**
     * @param key
     * @param propertyNames
     * @return string that correspond to key
     */
    private String getProperty(String key, Properties propertyNames) {
        return Property.getProperty(getRealmKey(key, propertyNames));
    }

    /**
     * @param key
     * @param propertyNames
     * @return boolean that correspond to key
     */
    private boolean getPropertyBoolean(String key, Properties propertyNames) {
        return Property.getPropertyBoolean(getRealmKey(key, propertyNames));
    }

    /**
     * @param key
     * @param propertyNames
     * @return int thtat correspond to key
     */
    private int getPropertyInt(String key, Properties propertyNames) {
        return Property.getPropertyInt(getRealmKey(key, propertyNames));
    }

    /**
     * @param key
     * @param propertyNames
     * @return RealmKey that correspond to key
     */
    private RealmKey getRealmKey(String key, Properties propertyNames) {
        String propertyOrDefault = propertyNames.getProperty(key, key);
        return new RealmKey(propertyOrDefault, realm);
    }

}