All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.james.user.ldap.ReadOnlyUsersLDAPRepository Maven / Gradle / Ivy

/****************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one   *
 * or more contributor license agreements.  See the NOTICE file *
 * distributed with this work for additional information        *
 * regarding copyright ownership.  The ASF licenses this file   *
 * to you 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.apache.james.user.ldap;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.logging.Log;
import org.apache.james.lifecycle.Configurable;
import org.apache.james.lifecycle.LogEnabled;
import org.apache.james.user.api.User;
import org.apache.james.user.api.UsersRepository;

/**
 * 

* This repository implementation serves as a bridge between Apache James and * LDAP. It allows James to authenticate users against an LDAP compliant server * such as Apache DS or Microsoft AD. It also enables role/group based access * restriction based on LDAP groups. *

*

* It is intended for organisations that already have a user-authentication and * authorisation mechanism in place, and want to leverage this when deploying * James. The assumption inherent here is that such organisations would not want * to manage user details via James, but will do so externally using whatever * mechanism provided by, or built on top off, their LDAP implementation. *

*

* Based on this assumption, this repository is strictly read-only. As a * consequence, user modification, deletion and creation requests will be * ignored when using this repository. *

*

* The following fragment of XML provides an example configuration to enable * this repository:
* *

 *  <users-store>
 *      <repository name="LDAPUsers" 
 *      class="org.apache.james.userrepository.ReadOnlyUsersLDAPRepository" 
 *      ldapHost="ldap://myldapserver:389"
 *      principal="uid=ldapUser,ou=system"
 *      credentials="password"
 *      userBase="ou=People,o=myorg.com,ou=system"
 *      userIdAttribute="uid"/>
 *      userObjectClass="inetOrgPerson"/>
 *  </users-store>
 * 
* *
* * Its constituent attributes are defined as follows: *
    *
  • ldapHost: The URL of the LDAP server to connect to.
  • *
  • * principal: The name (DN) of the user with which to initially bind to * the LDAP server.
  • *
  • * credentials: The password with which to initially bind to the LDAP * server.
  • *
  • * userBase:The context within which to search for user entities.
  • *
  • * userIdAttribute:The name of the LDAP attribute which holds user ids. * For example "uid" for Apache DS, or "sAMAccountName" for * Microsoft Active Directory.
  • *
  • * userObjectClass:The objectClass value for user nodes below the userBase. * For example "inetOrgPerson" for Apache DS, or "user" for * Microsoft Active Directory. *
  • *
*

* *

* In order to enable group/role based access restrictions, you can use the * "<restriction>" configuration element. An example of this is * shown below:
* *

 * <restriction
 * 	memberAttribute="uniqueMember">
 * 		<group>cn=PermanentStaff,ou=Groups,o=myorg.co.uk,ou=system</group>
 *        	<group>cn=TemporaryStaff,ou=Groups,o=myorg.co.uk,ou=system</group>
 * </restriction>
 *
* *
* Its constituent attributes and elements are defined as follows: *
    *
  • * memberAttribute: The LDAP attribute whose values indicate the DNs of * the users which belong to the group or role.
  • *
  • * group: A valid group or role DN. A user is only authenticated * (permitted access) if they belong to at least one of the groups listed under * the "<restriction>" sections.
  • *
*

* * @see SimpleLDAPConnection * @see ReadOnlyLDAPUser * @see ReadOnlyLDAPGroupRestriction * */ public class ReadOnlyUsersLDAPRepository implements UsersRepository, Configurable, LogEnabled { /** *

* The URL of the LDAP server against which users are to be authenticated. * Note that users are actually authenticated by binding against the LDAP * server using the users "dn" and "credentials".The * value of this field is taken from the value of the configuration * attribute "ldapHost". *

*/ private String ldapHost; /** *

* The value of this field is taken from the configuration attribute * "userIdAttribute". This is the LDAP attribute type which holds * the userId value. Note that this is not the same as the email address * attribute. *

*/ private String userIdAttribute; /** *

* The value of this field is taken from the configuration attribute * "userObjectClass". This is the LDAP object class to use * in the search filter for user nodes under the userBase value. *

*/ private String userObjectClass; /** *

* This is the LDAP context/sub-context within which to search for user * entities. The value of this field is taken from the configuration * attribute "userBase". *

*/ private String userBase; /** *

* The user with which to initially bind to the LDAP server. The value of * this field is taken from the configuration attribute * "principal". *

*/ private String principal; /** *

* The password/credentials with which to initially bind to the LDAP server. * The value of this field is taken from the configuration attribute * "credentials". *

*/ private String credentials; /** *

* Encapsulates the information required to restrict users to LDAP groups or * roles. This object is populated from the contents of the configuration * element <restriction>. *

*/ private ReadOnlyLDAPGroupRestriction restriction; /** *

* The connection handle to the LDAP server. This is the connection that is * built from the configuration attributes "ldapHost", * "principal" and "credentials". *

*/ private SimpleLDAPConnection ldapConnection; private Log log; /** *

* Extracts the parameters required by the repository instance from the * James server configuration data. The fields extracted include * {@link #ldapHost}, {@link #userIdAttribute}, {@link #userBase}, * {@link #principal}, {@link #credentials} and {@link #restriction}. *

* * @param configuration * An encapsulation of the James server configuration data. */ public void configure(HierarchicalConfiguration configuration) throws ConfigurationException { ldapHost = configuration.getString("[@ldapHost]"); principal = configuration.getString("[@principal]"); credentials = configuration.getString("[@credentials]"); userBase = configuration.getString("[@userBase]"); userIdAttribute = configuration.getString("[@userIdAttribute]"); userObjectClass = configuration.getString("[@userObjectClass]"); restriction = new ReadOnlyLDAPGroupRestriction(configuration.configurationAt("restriction")); } /** *

* Initialises the user-repository instance. It will create a connection to * the LDAP host using the supplied configuration. *

* * @throws Exception * If an error occurs authenticating or connecting to the * specified LDAP host. */ @PostConstruct public void init() throws Exception { StringBuffer logBuffer; if (log.isDebugEnabled()) { logBuffer = new StringBuffer(128).append(this.getClass().getName()).append(".initialize()"); log.debug(logBuffer.toString()); logBuffer = new StringBuffer(256).append("Openning connection to LDAP host: ").append(ldapHost).append("."); log.debug(logBuffer.toString()); } ldapConnection = SimpleLDAPConnection.openLDAPConnection(principal, credentials, ldapHost); if (log.isDebugEnabled()) { logBuffer = new StringBuffer(256).append("Initialization complete. User baseDN=").append(userBase).append(" ; userIdAttribute=" + userIdAttribute).append("\n\tGroup restriction:" + restriction); log.debug(logBuffer.toString()); } } /** *

* Indicates if the user with the specified DN can be found in the group * membership map-as encapsulated by the specified parameter map. *

* * @param userDN * The DN of the user to search for. * @param groupMembershipList * A map containing the entire group membership lists for the * configured groups. This is organised as a map of * * "<groupDN>=<[userDN1,userDN2,...,userDNn]>" * pairs. In essence, each groupDN string is * associated to a list of userDNs. * @return True if the specified userDN is associated with at * least one group in the parameter map, and False * otherwise. */ private boolean userInGroupsMembershipList(String userDN, Map> groupMembershipList) { boolean result = false; Collection> memberLists = groupMembershipList.values(); Iterator> memberListsIterator = memberLists.iterator(); while (memberListsIterator.hasNext() && !result) { Collection groupMembers = memberListsIterator.next(); result = groupMembers.contains(userDN); } return result; } /** *

* Gets all the user entities taken from the LDAP server, as taken from the * search-context given by the value of the attribute {@link #userBase}. *

* * @return A set containing all the relevant users found in the LDAP * directory. * @throws NamingException * Propagated from the LDAP communication layer. */ private Set getAllUsersFromLDAP() throws NamingException { Set result = new HashSet(); SearchControls sc = new SearchControls (); sc.setSearchScope (SearchControls.SUBTREE_SCOPE); sc.setReturningAttributes (new String[] { "distinguishedName" }); NamingEnumeration sr = ldapConnection.getLdapContext ().search (userBase, "(objectClass=" + userObjectClass + ")", sc); while(sr.hasMore ()) { SearchResult r = sr.next (); result.add (r.getNameInNamespace ()); } return result; } /** *

* Extract the user attributes for the given collection of userDNs, and * encapsulates the user list as a collection of {@link ReadOnlyLDAPUser}s. * This method delegates the extraction of a single user's details to the * method {@link #buildUser(String)}. *

* * @param userDNs * The distinguished-names (DNs) of the users whose information * is to be extracted from the LDAP repository. * @return A collection of {@link ReadOnlyLDAPUser}s as taken from the LDAP * server. * @throws NamingException * Propagated from the underlying LDAP communication layer. */ private Collection buildUserCollection(Collection userDNs) throws NamingException { List results = new ArrayList(); Iterator userDNIterator = userDNs.iterator(); while (userDNIterator.hasNext()) { ReadOnlyLDAPUser user = buildUser(userDNIterator.next()); results.add(user); } return results; } /** *

* Given a userDN, this method retrieves the user attributes from the LDAP * server, so as to extract the items that are of interest to James. * Specifically it extracts the userId, which is extracted from the LDAP * attribute whose name is given by the value of the field * {@link #userIdAttribute}. *

* * @param userDN * The distinguished-name of the user whose details are to be * extracted from the LDAP repository. * @return A {@link ReadOnlyLDAPUser} instance which is initialized with the * userId of this user and ldap connection information with which * the userDN and attributes were obtained. * @throws NamingException * Propagated by the underlying LDAP communication layer. */ private ReadOnlyLDAPUser buildUser(String userDN) throws NamingException { ReadOnlyLDAPUser result; Attributes userAttributes = ldapConnection.getLdapContext().getAttributes(userDN); Attribute userName = userAttributes.get(userIdAttribute); result = new ReadOnlyLDAPUser(userName.get().toString(), userDN, ldapHost); return result; } /* * (non-Javadoc) * * @see org.apache.james.api.user.UsersRepository#contains(java.lang.String) */ public boolean contains(String name) { if (getUserByName(name) != null) { return true; } return false; } /* * (non-Javadoc) * * @see * org.apache.james.api.user.UsersRepository#containsCaseInsensitive(java * .lang.String) */ public boolean containsCaseInsensitive(String name) { if (getUserByNameCaseInsensitive(name) != null) { return true; } return false; } /* * (non-Javadoc) * * @see org.apache.james.api.user.UsersRepository#countUsers() */ public int countUsers() { try { return getValidUsers().size(); } catch (NamingException e) { log.error("Unable to retrieve user count from ldap", e); } return 0; } /* * (non-Javadoc) * * @see * org.apache.james.api.user.UsersRepository#getRealName(java.lang.String) */ public String getRealName(String name) { User u = getUserByNameCaseInsensitive(name); if (u != null) { return u.getUserName(); } return null; } /* * (non-Javadoc) * * @see * org.apache.james.api.user.UsersRepository#getUserByName(java.lang.String) */ public User getUserByName(String name) { try { Iterator userIt = buildUserCollection(getValidUsers()).iterator(); while (userIt.hasNext()) { ReadOnlyLDAPUser u = userIt.next(); if (u.getUserName().equals(name)) { return u; } } } catch (NamingException e) { log.error("Unable to retrieve user from ldap", e); } return null; } /* * (non-Javadoc) * * @see * org.apache.james.api.user.UsersRepository#getUserByNameCaseInsensitive * (java.lang.String) */ public User getUserByNameCaseInsensitive(String name) { try { Iterator userIt = buildUserCollection(getValidUsers()).iterator(); while (userIt.hasNext()) { ReadOnlyLDAPUser u = userIt.next(); if (u.getUserName().equalsIgnoreCase(name)) { return u; } } } catch (NamingException e) { log.error("Unable to retrieve user from ldap", e); } return null; } /* * (non-Javadoc) * * @see org.apache.james.api.user.UsersRepository#list() */ public Iterator list() { List result = new ArrayList(); try { Iterator userIt = buildUserCollection(getValidUsers()).iterator(); while (userIt.hasNext()) { result.add(userIt.next().getUserName()); } } catch (NamingException namingException) { throw new RuntimeException("Unable to retrieve users list from LDAP due to unknown naming error.", namingException); } return result.iterator(); } private Collection getValidUsers() throws NamingException { Set userDNs = getAllUsersFromLDAP(); Collection validUserDNs; if (restriction.isActivated()) { Map> groupMembershipList = restriction.getGroupMembershipLists(ldapConnection); validUserDNs = new ArrayList(); Iterator userDNIterator = userDNs.iterator(); String userDN; while (userDNIterator.hasNext()) { userDN = userDNIterator.next(); if (userInGroupsMembershipList(userDN, groupMembershipList)) validUserDNs.add(userDN); } } else { validUserDNs = userDNs; } return validUserDNs; } /* * (non-Javadoc) * * @see * org.apache.james.api.user.UsersRepository#removeUser(java.lang.String) */ public void removeUser(String name) { log.warn("This user-repository is read-only. Modifications are not permitted."); } /* * (non-Javadoc) * * @see org.apache.james.api.user.UsersRepository#test(java.lang.String, * java.lang.String) */ public boolean test(String name, String password) { User u = getUserByName(name); if (u != null) { return u.verifyPassword(password); } return false; } /* * (non-Javadoc) * * @see * org.apache.james.api.user.UsersRepository#addUser(org.apache.james.api * .user.User) */ public boolean addUser(User user) { log.warn("This user-repository is read-only. Modifications are not permitted."); return false; } /* * (non-Javadoc) * * @see org.apache.james.api.user.UsersRepository#addUser(java.lang.String, * java.lang.Object) */ public void addUser(String name, Object attributes) { log.warn("This user-repository is read-only. Modifications are not permitted."); } /* * (non-Javadoc) * * @see org.apache.james.api.user.UsersRepository#addUser(java.lang.String, * java.lang.String) */ public boolean addUser(String username, String password) { log.warn("This user-repository is read-only. Modifications are not permitted."); return false; } /* * (non-Javadoc) * * @see * org.apache.james.api.user.UsersRepository#updateUser(org.apache.james * .api.user.User) */ public boolean updateUser(User user) { log.warn("This user-repository is read-only. Modifications are not permitted."); return false; } /* * (non-Javadoc) * * @see * org.apache.james.lifecycle.LogEnabled#setLog(org.apache.commons.logging * .Log) */ public void setLog(Log log) { this.log = log; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy