org.projectforge.business.ldap.LdapUserDao.java Source code

Java tutorial

Introduction

Here is the source code for org.projectforge.business.ldap.LdapUserDao.java

Source

/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition 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; version 3 of the License.
//
// This community edition 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, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.business.ldap;

import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.projectforge.framework.utils.NumberHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import arlut.csd.crypto.SmbEncrypt;

/**
 * @author Kai Reinhard (k.reinhard@micromata.de)
 */
@Service
public class LdapUserDao extends LdapDao<String, LdapUser> {
    private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LdapUserDao.class);

    public static final String DEACTIVATED_SUB_CONTEXT = "deactivated";

    private static final String DEACTIVATED_SUB_CONTEXT2 = "ou=" + DEACTIVATED_SUB_CONTEXT;

    private static final String DEACTIVATED_SUB_CONTEXT3 = DEACTIVATED_SUB_CONTEXT2 + ",";

    static final String DEACTIVATED_MAIL = "deactivated@localhost";

    public static final String RESTRICTED_USER_SUB_CONTEXT = "restricted";

    private static final String RESTRICTED_USER_SUB_CONTEXT2 = "ou=" + RESTRICTED_USER_SUB_CONTEXT;

    private static final String RESTRICTED_USER_SUB_CONTEXT3 = RESTRICTED_USER_SUB_CONTEXT2 + ",";

    private boolean useUidInDn = false;

    @Autowired
    private LdapPersonDao ldapPersonDao;

    static String[] ALL_OBJECT_CLASSES;

    static String[] ALL_OBJECT_CLASSES_WITH_POSIX_ACCOUNT;

    static String[] ALL_OBJECT_CLASSES_WITH_SAMBA_ACCOUNT;

    static String[] ALL_OBJECT_CLASSES_WITH_SAMBA_AND_POSIX_ACCOUNT;

    private static String POSIX_OBJECT_CLASS = "posixAccount";

    private static String SAMBA_OBJECT_CLASS = "sambaSamAccount";

    @Autowired
    LdapService ldapService;

    @PostConstruct
    public void init() {
        useUidInDn = true;
    }

    public boolean isDeactivated(final LdapUser user) {
        return user.isDeactivated() || user.getOrganizationalUnit() != null
                && LdapUtils.getOu(user.getOrganizationalUnit()).contains(DEACTIVATED_SUB_CONTEXT) == true;
    }

    public boolean isRestrictedUser(final LdapUser user) {
        return user.isRestrictedUser() || user.getOrganizationalUnit() != null
                && LdapUtils.getOu(user.getOrganizationalUnit()).contains(RESTRICTED_USER_SUB_CONTEXT) == true;
    }

    public boolean isPosixAccountsConfigured() {
        final LdapConfig ldapConfig = ldapService.getLdapConfig();
        if (ldapConfig == null) {
            return false;
        }
        final LdapPosixAccountsConfig posixAccountsConfig = ldapConfig.getPosixAccountsConfig();
        return posixAccountsConfig != null;
    }

    public boolean isSambaAccountsConfigured() {
        final LdapConfig ldapConfig = ldapService.getLdapConfig();
        if (ldapConfig == null) {
            return false;
        }
        final LdapSambaAccountsConfig sambaAccountsConfig = ldapConfig.getSambaAccountsConfig();
        return sambaAccountsConfig != null
                && StringUtils.isNotBlank(sambaAccountsConfig.getSambaSIDPrefix()) == true;
    }

    /**
     * @see org.projectforge.business.ldap.LdapDao#getObjectClass()
     */
    @Override
    protected String getObjectClass() {
        return ldapPersonDao.getObjectClass();
    }

    /**
     * @see org.projectforge.business.ldap.LdapDao#getAdditionalObjectClasses()
     */
    @Override
    protected String[] getAdditionalObjectClasses() {
        throw new UnsupportedOperationException("Call getAdditionalObjectClasses(LdapUser) instead.");
    }

    void initializeObjectClasses() {
        if (ALL_OBJECT_CLASSES != null) {
            // Already initialized.
            return;
        }
        final List<String> additionalObjectClassesList = new LinkedList<String>();
        for (final String additionalObjectClass : ldapPersonDao.getAdditionalObjectClasses()) {
            additionalObjectClassesList.add(additionalObjectClass);
        }
        ALL_OBJECT_CLASSES = additionalObjectClassesList.toArray(new String[0]);
        additionalObjectClassesList.add(POSIX_OBJECT_CLASS);
        ALL_OBJECT_CLASSES_WITH_POSIX_ACCOUNT = additionalObjectClassesList.toArray(new String[0]);
        additionalObjectClassesList.add(SAMBA_OBJECT_CLASS);
        ALL_OBJECT_CLASSES_WITH_SAMBA_AND_POSIX_ACCOUNT = additionalObjectClassesList.toArray(new String[0]);
        additionalObjectClassesList.clear();
        for (final String additionalObjectClass : ldapPersonDao.getAdditionalObjectClasses()) {
            additionalObjectClassesList.add(additionalObjectClass);
        }
        additionalObjectClassesList.add(SAMBA_OBJECT_CLASS);
        ALL_OBJECT_CLASSES_WITH_SAMBA_ACCOUNT = additionalObjectClassesList.toArray(new String[0]);
    }

    /**
     * @see org.projectforge.business.ldap.LdapDao#getAdditionalObjectClasses(org.projectforge.business.ldap.LdapObject)
     */
    @Override
    protected String[] getAdditionalObjectClasses(final LdapUser obj) {
        final boolean posixAccount = isPosixAccountsConfigured() == true
                && PFUserDOConverter.isPosixAccountValuesEmpty(obj) == false;
        final boolean sambaAccount = isSambaAccountsConfigured() == true
                && PFUserDOConverter.isSambaAccountValuesEmpty(obj) == false;
        if (ALL_OBJECT_CLASSES == null) {
            initializeObjectClasses();
        }
        if (posixAccount == true) {
            if (sambaAccount == true) {
                return ALL_OBJECT_CLASSES_WITH_SAMBA_AND_POSIX_ACCOUNT;
            }
            return ALL_OBJECT_CLASSES_WITH_POSIX_ACCOUNT;
        }
        if (sambaAccount == true) {
            return ALL_OBJECT_CLASSES_WITH_SAMBA_ACCOUNT;
        }
        return ALL_OBJECT_CLASSES;
    }

    /**
     * @see org.projectforge.business.ldap.LdapDao#getIdAttrId()
     */
    @Override
    public String getIdAttrId() {
        return "employeeNumber";
    }

    /**
     * @see org.projectforge.business.ldap.LdapDao#getId(org.projectforge.business.ldap.LdapUser)
     */
    @Override
    public String getId(final LdapUser obj) {
        return obj.getEmployeeNumber();
    }

    /**
     * @see org.projectforge.business.ldap.LdapDao#mapToObject(java.lang.String, javax.naming.directory.Attributes)
     */
    @Override
    protected LdapUser mapToObject(final String dn, final Attributes attributes) throws NamingException {
        final LdapUser user = new LdapUser();
        ldapPersonDao.mapToObject(dn, user, attributes);
        ldapConfig = ldapService.getLdapConfig();
        final boolean posixAccountsConfigured = isPosixAccountsConfigured();
        final boolean sambaAccountsConfigured = isSambaAccountsConfigured();
        if (posixAccountsConfigured == true || sambaAccountsConfigured == true) {
            final String no = LdapUtils.getAttributeStringValue(attributes, "uidNumber");
            user.setUidNumber(NumberHelper.parseInteger(no));
        }
        if (posixAccountsConfigured == true) {
            final String no = LdapUtils.getAttributeStringValue(attributes, "gidNumber");
            user.setGidNumber(NumberHelper.parseInteger(no));
            user.setHomeDirectory(LdapUtils.getAttributeStringValue(attributes, "homeDirectory"));
            user.setLoginShell(LdapUtils.getAttributeStringValue(attributes, "loginShell"));
        }
        if (sambaAccountsConfigured == true) {
            final String sambaSID = LdapUtils.getAttributeStringValue(attributes, "sambaSID");
            final Integer sambaSIDNumber = ldapConfig.getSambaAccountsConfig().getSambaSIDNumber(sambaSID);
            user.setSambaSIDNumber(sambaSIDNumber);
            final String sambaPrimaryGroupSID = LdapUtils.getAttributeStringValue(attributes,
                    "sambaPrimaryGroupSID");
            final Integer sambaPrimaryGroupSIDNumber = ldapConfig.getSambaAccountsConfig()
                    .getSambaSIDNumber(sambaPrimaryGroupSID);
            user.setSambaPrimaryGroupSIDNumber(sambaPrimaryGroupSIDNumber);
            user.setSambaNTPassword(LdapUtils.getAttributeStringValue(attributes, "sambaNTPassword"));
            final String sambaPwdLastSet = LdapUtils.getAttributeStringValue(attributes, "sambaPwdLastSet");
            if (sambaPwdLastSet != null) {
                final long value = NumberHelper.parseLong(sambaPwdLastSet) * 1000; // ms since 1970
                user.setSambaPwdLastSet(new Date(value));
            }
        }
        if (dn != null) {
            if (dn.contains(DEACTIVATED_SUB_CONTEXT2) == true) {
                user.setDeactivated(true);
            }
            if (dn.contains(RESTRICTED_USER_SUB_CONTEXT2) == true) {
                user.setRestrictedUser(true);
            }

        }
        final Object userPassword = LdapUtils.getAttributeValue(attributes, "userPassword");
        if (userPassword != null) {
            user.setPasswordGiven(true);
        }
        return user;
    }

    public void deactivateUser(final LdapUser user) {
        new LdapTemplate(ldapConnector) {
            @Override
            protected Object call() throws NameNotFoundException, Exception {
                deactivateUser(ctx, user);
                return null;
            }
        }.excecute();
    }

    public void deactivateUser(final DirContext ctx, final LdapUser user) throws NamingException {
        log.info("Deactivate user: " + buildDn(null, user));
        final List<ModificationItem> modificationItems = new ArrayList<ModificationItem>();
        modificationItems
                .add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userPassword", null)));
        modificationItems.add(
                new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("mail", DEACTIVATED_MAIL)));
        buildDn(null, user);
        modify(ctx, user, modificationItems);
        final String ou = user.getOrganizationalUnit();
        if (ou.startsWith(DEACTIVATED_SUB_CONTEXT2) == false) {
            // Move user to the sub-context "deactivated".
            final String newOu = LdapUtils.getOu(DEACTIVATED_SUB_CONTEXT, getOuBase());
            move(ctx, user, newOu);
            user.setOrganizationalUnit(newOu);
        }
    }

    @Override
    public String getOuBase() {
        return ldapConfig.getUserBase();
    }

    /**
     * Moves the user only from the "deactivated" sub-context to the parent context. If the user isn't in the context name
     * "deactivated" nothing will be done.
     * 
     * @param user
     */
    public void reactivateUser(final LdapUser user) {
        new LdapTemplate(ldapConnector) {
            @Override
            protected Object call() throws NameNotFoundException, Exception {
                reactivateUser(ctx, user);
                return null;
            }
        }.excecute();
    }

    public void reactivateUser(final DirContext ctx, final LdapUser user) throws NamingException {
        log.info("Reactivate deactivated user: " + buildDn(null, user));
        final String ou = LdapUtils.getOu(user.getOrganizationalUnit());
        if (ou.startsWith(DEACTIVATED_SUB_CONTEXT2) == false) {
            log.info("Object isn't in a deactivated sub-context, nothing will be done: " + buildDn(null, user));
            return;
        }
        String newPath;
        if (ou.startsWith(DEACTIVATED_SUB_CONTEXT3) == true) {
            newPath = ou.substring(DEACTIVATED_SUB_CONTEXT3.length());
        } else {
            newPath = ou.substring(DEACTIVATED_SUB_CONTEXT2.length());
        }
        move(ctx, user, newPath);
        user.setOrganizationalUnit(newPath);
    }

    void updateActivatedStatus(final DirContext ctx, final LdapUser user) throws NamingException {
        final String ou = LdapUtils.getOu(user.getOrganizationalUnit());
        if (user.isDeactivated() == true) {
            if (ou.startsWith(DEACTIVATED_SUB_CONTEXT2) == true) {
                // User is already stored in deactivated context. Nothing to be done.
                return;
            } else {
                deactivateUser(ctx, user);
            }
        } else {
            if (ou.startsWith(DEACTIVATED_SUB_CONTEXT2) == false) {
                // User isn't stored in deactivated context. Nothing to be done.
                return;
            } else {
                reactivateUser(ctx, user);
            }
        }
    }

    void updateRestrictedUserStatus(final DirContext ctx, final LdapUser user) throws NamingException {
        final String ou = LdapUtils.getOu(user.getOrganizationalUnit());
        if (user.isDeactivated() == true) {
            // User is deactivated, thus the restricted-user-status is ignored.
            return;
        }
        if (user.isRestrictedUser() == true) {
            if (ou.startsWith(RESTRICTED_USER_SUB_CONTEXT2) == true) {
                // User is already stored in restricted context. Nothing to be done.
                return;
            } else {
                setUserAsRestrictedUser(ctx, user);
            }
        } else {
            if (ou.startsWith(RESTRICTED_USER_SUB_CONTEXT2) == false) {
                // User isn't stored in restricted context. Nothing to be done.
                return;
            } else {
                log.info("Move user from restricted sub context: " + buildDn(null, user));
                String newPath;
                if (ou.startsWith(RESTRICTED_USER_SUB_CONTEXT3) == true) {
                    newPath = ou.substring(RESTRICTED_USER_SUB_CONTEXT3.length());
                } else {
                    newPath = ou.substring(RESTRICTED_USER_SUB_CONTEXT2.length());
                }
                move(ctx, user, newPath);
                user.setOrganizationalUnit(newPath);
            }
        }
    }

    private void setUserAsRestrictedUser(final DirContext ctx, final LdapUser user) throws NamingException {
        log.info("Move user to restricted sub context: " + buildDn(null, user));
        if (user.isDeactivated() == true) {
            log.info("User is deactivated, thus the restricted-user-status is ignored: " + buildDn(null, user));
            return;
        }
        final String ou = user.getOrganizationalUnit();
        if (ou.startsWith(RESTRICTED_USER_SUB_CONTEXT2) == false) {
            // Move user to the sub-context "restricted".
            final String newOu = LdapUtils.getOu(RESTRICTED_USER_SUB_CONTEXT, user.getOrganizationalUnit());
            move(ctx, user, newOu);
            user.setOrganizationalUnit(newOu);
        }
    }

    /**
     * Calls super method and {@link #deactivateUser(DirContext, LdapUser)} if the given user is deactivated. If the given
     * user is deleted, nothing will be done.
     * 
     * @see org.projectforge.business.ldap.LdapDao#create(javax.naming.directory.DirContext, org.projectforge.business.ldap.LdapObject,
     *      java.lang.Object[])
     */
    @Override
    public void create(final DirContext ctx, final String ouBase, final LdapUser user, final Object... args)
            throws NamingException {
        if (user.isDeleted() == true) {
            log.info(
                    "Given LDAP user is deleted, so the user will not be created in the LDAP system (nothing will be done).");
            return;
        }
        super.create(ctx, ouBase, user, args);
        if (user.isDeactivated() == true) {
            deactivateUser(ctx, user);
        } else if (user.isRestrictedUser() == true) {
            // Deactivated users shouldn't be moved to restricted ou sub context.
            setUserAsRestrictedUser(ctx, user);
        }
    }

    /**
     * @see org.projectforge.business.ldap.LdapDao#update(javax.naming.directory.DirContext, org.projectforge.business.ldap.LdapObject,
     *      java.lang.Object[])
     */
    @Override
    public void update(final DirContext ctx, final String ouBase, final LdapUser user, final Object... objs)
            throws NamingException {
        if (user.isDeleted() == true) {
            log.info("Given LDAP user is deleted, so the user will be removed from the LDAP system.");
            delete(ctx, user);
            return;
        }
        super.update(ctx, ouBase, user, objs);
        updateActivatedStatus(ctx, user);
        updateRestrictedUserStatus(ctx, user);
    }

    public void changePassword(final LdapUser user, final String oldPassword, final String newPassword) {
        log.info("Change password for " + getObjectClass() + ": " + buildDn(null, user));
        final List<ModificationItem> modificationItems = new ArrayList<ModificationItem>();
        if (oldPassword != null) {
            modificationItems.add(new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
                    new BasicAttribute("userPassword", oldPassword)));
            modificationItems.add(new ModificationItem(DirContext.ADD_ATTRIBUTE,
                    new BasicAttribute("userPassword", newPassword)));
        } else {
            modificationItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                    new BasicAttribute("userPassword", newPassword)));
        }
        if (isSambaAccountsConfigured() == true && user.getSambaSIDNumber() != null) {
            final String sambaNTPassword = SmbEncrypt.NTUNICODEHash(newPassword);
            modificationItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                    new BasicAttribute("sambaNTPassword", sambaNTPassword)));
        }
        // Perform the update
        modify(user, modificationItems);
    }

    public LdapUser findByUsername(final Object username, final String... organizationalUnits) {
        return (LdapUser) new LdapTemplate(ldapConnector) {
            @Override
            protected Object call() throws NameNotFoundException, Exception {
                NamingEnumeration<?> results = null;
                final SearchControls controls = new SearchControls();
                controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
                final String searchBase = getSearchBase(organizationalUnits);
                results = ctx.search(searchBase, "(&(objectClass=" + getObjectClass() + ")(uid=" + username + "))",
                        controls);
                if (results.hasMore() == false) {
                    return null;
                }
                final SearchResult searchResult = (SearchResult) results.next();
                final String dn = searchResult.getName();
                final Attributes attributes = searchResult.getAttributes();
                if (results.hasMore() == true) {
                    log.error("Oups, found entries with multiple id's: " + getObjectClass() + "." + username);
                }
                return mapToObject(dn, searchBase, attributes);
            }
        }.excecute();
    }

    public LdapUser authenticate(final String username, final String userPassword,
            final String... organizationalUnits) {
        String dn;
        LdapUser user = null;
        final String searchBase = getSearchBase(organizationalUnits);
        if (StringUtils.isNotBlank(ldapConfig.getManagerUser()) == true
                && StringUtils.isNotBlank(ldapConfig.getManagerPassword()) == true) {
            user = findByUsername(username, searchBase);
            if (user == null || StringUtils.equals(username, user.getId()) == false) {
                log.info("User with id '" + username + "' not found.");
                return null;
            }
            dn = user.getDn() + "," + ldapConnector.getBase();
        } else {
            dn = "uid=" + username + "," + searchBase + "," + ldapConnector.getBase();
        }
        try {
            ldapConnector.createContext(dn, userPassword);
            log.info("User '" + username + "' (" + dn + ") successfully authenticated.");
            return user;
        } catch (final Exception ex) {
            log.error("User '" + username + "' (" + dn + ") with invalid credentials.");
            return null;
        }
    }

    /**
     * @see org.projectforge.business.ldap.LdapDao#createAndAddModificationItems(java.util.List, java.lang.String,
     *      java.lang.String[])
     */
    @Override
    protected void createAndAddModificationItems(final List<ModificationItem> list, final String attrId,
            final String... attrValues) {
        if ("uid".equals(attrId) == true) {
            // Don't change uid because it's part of the dn.
            return;
        }
        super.createAndAddModificationItems(list, attrId, attrValues);
    }

    /**
     * @see org.projectforge.ldap.LdapPDao#getModificationItems(java.util.List, org.projectforge.business.ldap.LdapUser)
     */
    @Override
    protected List<ModificationItem> getModificationItems(List<ModificationItem> list, final LdapUser user) {
        list = ldapPersonDao.getModificationItems(list, user);
        createAndAddModificationItems(list, "cn", user.getCommonName());
        final boolean modifyPosixAccount = isPosixAccountsConfigured() == true
                && PFUserDOConverter.isPosixAccountValuesEmpty(user) == false;
        final boolean modifySambaAccount = isSambaAccountsConfigured() == true
                && PFUserDOConverter.isSambaAccountValuesEmpty(user) == false;
        if (modifyPosixAccount == true || modifySambaAccount == true) {
            if (user.getObjectClasses() != null) {
                final List<String> missedObjectClasses = LdapUtils.getMissedObjectClasses(
                        getAdditionalObjectClasses(user), getObjectClass(), user.getObjectClasses());
                if (CollectionUtils.isNotEmpty(missedObjectClasses) == true) {
                    for (final String missedObjectClass : missedObjectClasses) {
                        list.add(
                                createModificationItem(DirContext.ADD_ATTRIBUTE, "objectClass", missedObjectClass));
                    }
                }
            }
        }
        if (modifyPosixAccount == true) {
            createAndAddModificationItems(list, "uidNumber", String.valueOf(user.getUidNumber()));
            createAndAddModificationItems(list, "gidNumber", String.valueOf(user.getGidNumber()));
            createAndAddModificationItems(list, "homeDirectory", user.getHomeDirectory());
            createAndAddModificationItems(list, "loginShell", user.getLoginShell());
        }
        if (modifySambaAccount == true) {
            createAndAddModificationItems(list, "sambaSID",
                    ldapConfig.getSambaAccountsConfig().getSambaSID(user.getSambaSIDNumber()));
            createAndAddModificationItems(list, "sambaPrimaryGroupSID", ldapConfig.getSambaAccountsConfig()
                    .getSambaPrimaryGroupSID(user.getSambaPrimaryGroupSIDNumber()));
            createAndAddModificationItems(list, "sambaAcctFlags", "U          ");
            createAndAddModificationItems(list, "sambaPasswordHistory",
                    "0000000000000000000000000000000000000000000000000000000000000000");
            createAndAddModificationItems(list, "sambaPwdLastSet",
                    String.valueOf(user.getSambaPwdLastSetAsUnixEpochSeconds()));
        }
        return list;
    }

    /**
     * @see org.projectforge.business.ldap.LdapDao#buildDnIdentifier(org.projectforge.business.ldap.LdapObject)
     */
    @Override
    protected String buildDnIdentifier(final LdapUser obj) {
        if (useUidInDn == true) {
            return "uid=" + obj.getUid();
        } else {
            return "cn=" + LdapUtils.escapeCommonName(obj.getCommonName());
        }
    }

    /**
     * @see org.projectforge.business.ldap.LdapDao#buildId(java.lang.Object)
     */
    @Override
    protected String buildId(final Object id) {
        if (id == null) {
            return null;
        }
        if (id instanceof String && ((String) id).startsWith(PFUserDOConverter.ID_PREFIX) == true) {
            return String.valueOf(id);
        }
        return PFUserDOConverter.ID_PREFIX + id;
    }

    /**
     * @param ldapPersonDao the ldapPersonDao to set
     */
    public void setLdapPersonDao(final LdapPersonDao ldapPersonDao) {
        this.ldapPersonDao = ldapPersonDao;
    }
}