org.libreplan.web.users.services.LDAPCustomAuthenticationProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.libreplan.web.users.services.LDAPCustomAuthenticationProvider.java

Source

/*
 * This file is part of LibrePlan
 *
 * Copyright (C) 2011 ComtecSF S.L.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.libreplan.web.users.services;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.entities.ConfigurationRolesLDAP;
import org.libreplan.business.common.entities.LDAPConfiguration;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.users.daos.IUserDAO;
import org.libreplan.business.users.entities.User;
import org.libreplan.business.users.entities.UserRole;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.transaction.annotation.Transactional;

/**
 * An extending from AbstractUserDetailsAuthenticationProvider class which is
 * used to implement the authentication against LDAP.
 *
 * This provider implements the process explained in
 * <https://wiki.libreplan.org/twiki/bin/view/LibrePlan/AnA04S06LdapAuthentication>
 *
 * At this time it authenticates user against LDAP and then searches it in BD to
 * use the BD user in application.
 *
 * @author Ignacio Diaz Teijido <ignacio.diaz@comtecsf.es>
 * @author Cristina Alvarino Perez <cristina.alvarino@comtecsf.es>
 *
 */

// TODO resolve deprecated methods
public class LDAPCustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider
        implements AuthenticationProvider {

    @Autowired
    private IAdHocTransactionService transactionService;

    @Autowired
    private IConfigurationDAO configurationDAO;

    @Autowired
    private IUserDAO userDAO;

    private LDAPConfiguration configuration;

    /** Template to search in LDAP */
    private LdapTemplate ldapTemplate;

    private UserDetailsService userDetailsService;

    private DBPasswordEncoderService passwordEncoderService;

    private static final String COLON = ":";

    private static final String USER_ID_SUBSTITUTION = "[USER_ID]";

    private static final Log LOG = LogFactory.getLog(LDAPCustomAuthenticationProvider.class);

    /**
     * LDAP role matching could be configured using an asterix (*) to specify all users or groups
     */
    private static final String WILDCHAR_ALL = "*";

    @Override
    protected void additionalAuthenticationChecks(UserDetails arg0, UsernamePasswordAuthenticationToken arg1) {
        // No needed at this time
    }

    @Transactional(readOnly = true)
    @Override
    public UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) {

        String clearPassword = authentication.getCredentials().toString();

        if (StringUtils.isBlank(username) || StringUtils.isBlank(clearPassword)) {
            throw new BadCredentialsException("Username and password can not be empty");
        }

        String encodedPassword = passwordEncoderService.encodePassword(clearPassword, username);
        User user = getUserFromDB(username);

        // If user != null then exists in LibrePlan
        if (null != user && user.isLibrePlanUser()) {
            // is a LibrePlan user, then we must authenticate against DB
            return authenticateInDatabase(username, user, encodedPassword);
        }

        // If it's a LDAP or null user, then we must authenticate against LDAP

        // Load LDAPConfiguration properties
        configuration = loadLDAPConfiguration();

        if (configuration.getLdapAuthEnabled()) {
            // Sets the new context to ldapTemplate
            ldapTemplate.setContextSource(loadLDAPContext());

            try {

                // Test authentication for user against LDAP
                if (authenticateAgainstLDAP(username, clearPassword)) {

                    // Authentication against LDAP was ok
                    if (null == user) {

                        // User does not exist in LibrePlan must be imported
                        user = createLDAPUserWithRoles(username, encodedPassword);
                    } else {

                        // Update password
                        if (configuration.isLdapSavePasswordsDB()) {
                            user.setPassword(encodedPassword);
                        }

                        // Update roles from LDAP
                        setRoles(user);
                    }
                    saveUserOnTransaction(user);

                    return loadUserDetails(username);
                } else {
                    throw new BadCredentialsException("User is not in LDAP.");
                }
            } catch (Exception e) {
                // This exception captures when LDAP authentication is not possible
                LOG.info("LDAP not reachable. Trying to authenticate against database.", e);
            }
        }

        // LDAP is not enabled we must check if the LDAP user is in DB
        return authenticateInDatabase(username, user, encodedPassword);
    }

    private UserDetails loadUserDetails(String username) {
        return getUserDetailsService().loadUserByUsername(username);
    }

    private void setRoles(User user) {
        if (configuration.getLdapSaveRolesDB()) {
            user.clearRoles();
            List<String> roles = getMatchedRoles(configuration, user.getLoginName());
            for (String role : roles) {
                user.addRole(UserRole.valueOf(UserRole.class, role));
            }
        }
    }

    private User createLDAPUserWithRoles(String username, String encodedPassword) {
        User user = User.create();
        user.setLoginName(username);

        String newEncodedPassword = encodedPassword;

        // we must check if it is needed to save LDAP passwords in DB
        if (!configuration.isLdapSavePasswordsDB()) {
            newEncodedPassword = null;
        }

        user.setPassword(newEncodedPassword);
        user.setLibrePlanUser(false);
        user.setDisabled(false);
        setRoles(user);

        return user;
    }

    private LDAPConfiguration loadLDAPConfiguration() {
        return transactionService.runOnReadOnlyTransaction(new IOnTransaction<LDAPConfiguration>() {
            @Override
            public LDAPConfiguration execute() {
                return configurationDAO.getConfiguration().getLdapConfiguration();
            }
        });
    }

    private User getUserFromDB(String username) {
        final String usernameInserted = username;

        return transactionService.runOnReadOnlyTransaction(new IOnTransaction<User>() {
            @Override
            public User execute() {
                try {
                    return userDAO.findByLoginName(usernameInserted);
                } catch (InstanceNotFoundException e) {
                    LOG.info("User " + usernameInserted + " not found in database.");
                    return null;
                }
            }
        });

    }

    private LDAPCustomContextSource loadLDAPContext() {

        // Establishes the context for LDAP connection.
        LDAPCustomContextSource context = (LDAPCustomContextSource) ldapTemplate.getContextSource();

        context.setUrl(configuration.getLdapHost() + COLON + configuration.getLdapPort());
        context.setBase(configuration.getLdapBase());
        context.setUserDn(configuration.getLdapUserDn());
        context.setPassword(configuration.getLdapPassword());

        try {
            context.afterPropertiesSet();
        } catch (Exception e) {
            // This exception will be never reached if the LDAP properties are well-formed.
            LOG.error("There is a problem in LDAP connection: ", e);
        }

        return context;
    }

    private boolean authenticateAgainstLDAP(String username, String clearPassword) {
        return ldapTemplate.authenticate(DistinguishedName.EMPTY_PATH,
                new EqualsFilter(configuration.getLdapUserId(), username).toString(), clearPassword);
    }

    private void saveUserOnTransaction(User user) {
        final User librePlanUser = user;

        transactionService.runOnTransaction(new IOnTransaction<Void>() {
            @Override
            public Void execute() {
                userDAO.save(librePlanUser);
                return null;
            }
        });
    }

    private UserDetails authenticateInDatabase(String username, User user, String encodedPassword) {
        if (null != user && null != user.getPassword() && encodedPassword.equals(user.getPassword())) {
            return loadUserDetails(username);
        } else {
            throw new BadCredentialsException("Credentials are not the same as in database.");
        }
    }

    @SuppressWarnings("unchecked")
    private List<String> getRolesUsingNodeStrategy(Set<ConfigurationRolesLDAP> rolesLdap, String queryRoles,
            final LDAPConfiguration configuration) {

        String roleProperty = configuration.getLdapRoleProperty();

        List<String> rolesReturn = new ArrayList<>();
        for (ConfigurationRolesLDAP roleLDAP : rolesLdap) {
            if (roleLDAP.getRoleLdap().equals(WILDCHAR_ALL)) {
                rolesReturn.add(roleLDAP.getRoleLibreplan());
                continue;
            }

            // We must make a search for each role-matching in nodes
            List<Attribute> resultsSearch = new ArrayList<>();
            resultsSearch.addAll(ldapTemplate.search(DistinguishedName.EMPTY_PATH,
                    new EqualsFilter(roleProperty, roleLDAP.getRoleLdap()).toString(), new AttributesMapper() {
                        @Override
                        public Object mapFromAttributes(Attributes attributes) throws NamingException {
                            return attributes.get(configuration.getLdapUserId());
                        }
                    }));

            for (Attribute atrib : resultsSearch) {
                if (atrib.contains(queryRoles)) {
                    rolesReturn.add(roleLDAP.getRoleLibreplan());
                }
            }
        }

        return rolesReturn;
    }

    private List<String> getRolesUsingBranchStrategy(Set<ConfigurationRolesLDAP> rolesLdap, String queryRoles,
            LDAPConfiguration configuration) {

        String roleProperty = configuration.getLdapRoleProperty();
        String groupsPath = configuration.getLdapGroupPath();

        List<String> rolesReturn = new ArrayList<>();

        for (ConfigurationRolesLDAP roleLdap : rolesLdap) {
            if (roleLdap.getRoleLdap().equals(WILDCHAR_ALL)) {
                rolesReturn.add(roleLdap.getRoleLibreplan());
                continue;
            }

            // We must make a search for each role matching
            DirContextAdapter adapter = null;
            try {
                adapter = (DirContextAdapter) ldapTemplate.lookup(roleLdap.getRoleLdap() + "," + groupsPath);
            } catch (org.springframework.ldap.NamingException ne) {
                LOG.error(ne.getMessage());
            }
            if (adapter != null && adapter.attributeExists(roleProperty)) {
                Attributes atrs = adapter.getAttributes();

                if (atrs.get(roleProperty).contains(queryRoles)) {
                    rolesReturn.add(roleLdap.getRoleLibreplan());
                }
            }
        }

        return rolesReturn;
    }

    private List<String> getMatchedRoles(LDAPConfiguration configuration, String username) {

        String queryRoles = configuration.getLdapSearchQuery().replace(USER_ID_SUBSTITUTION, username);

        Set<ConfigurationRolesLDAP> rolesLdap = configuration.getConfigurationRolesLdap();

        try {
            if (!configuration.getLdapGroupStrategy()) {
                // The LDAP has a node strategy for groups, we must check the roleProperty in user node
                return getRolesUsingNodeStrategy(rolesLdap, queryRoles, configuration);
            } else {
                // The LDAP has a branch strategy for groups we must check if the user is in one of the groups
                return getRolesUsingBranchStrategy(rolesLdap, queryRoles, configuration);
            }
        } catch (Exception e) {
            LOG.error("Configuration of LDAP role-matching is wrong. Please check it.", e);
            return Collections.emptyList();
        }
    }

    public DBPasswordEncoderService getPasswordEncoderService() {
        return passwordEncoderService;
    }

    public void setPasswordEncoderService(DBPasswordEncoderService passwordEncoderService) {
        this.passwordEncoderService = passwordEncoderService;
    }

    public LdapTemplate getLdapTemplate() {
        return ldapTemplate;
    }

    public void setLdapTemplate(LdapTemplate ldapTemplate) {
        this.ldapTemplate = ldapTemplate;
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    public UserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

}