001/****************************************************************
002 * Licensed to the Apache Software Foundation (ASF) under one   *
003 * or more contributor license agreements.  See the NOTICE file *
004 * distributed with this work for additional information        *
005 * regarding copyright ownership.  The ASF licenses this file   *
006 * to you under the Apache License, Version 2.0 (the            *
007 * "License"); you may not use this file except in compliance   *
008 * with the License.  You may obtain a copy of the License at   *
009 *                                                              *
010 *   http://www.apache.org/licenses/LICENSE-2.0                 *
011 *                                                              *
012 * Unless required by applicable law or agreed to in writing,   *
013 * software distributed under the License is distributed on an  *
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015 * KIND, either express or implied.  See the License for the    *
016 * specific language governing permissions and limitations      *
017 * under the License.                                           *
018 ****************************************************************/
019package org.apache.james.user.ldap;
020
021import org.apache.commons.configuration.HierarchicalConfiguration;
022
023import javax.naming.NamingEnumeration;
024import javax.naming.NamingException;
025import javax.naming.directory.Attribute;
026import javax.naming.directory.Attributes;
027import javax.naming.ldap.LdapContext;
028import java.util.ArrayList;
029import java.util.Collection;
030import java.util.Collections;
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
034
035/**
036 * <p>
037 * Encapsulates the information required to restrict users to LDAP groups or
038 * roles. Instances of this type are populated from the contents of the
039 * <code>&lt;users-store&gt;</code> configuration child-element
040 * <code>&lt;restriction&gt;<code>.
041 * </p>
042 *
043 * @see ReadOnlyUsersLDAPRepository
044 * @see ReadOnlyLDAPUser
045 */
046
047public class ReadOnlyLDAPGroupRestriction {
048    /**
049     * The name of the LDAP attribute name which holds the unique names
050     * (distinguished-names/DNs) of the members of the group/role.
051     */
052    private String memberAttribute;
053
054    /**
055     * The distinguished-names of the LDAP groups/roles to which James users
056     * must belong. A user who is not a member of at least one of the groups or
057     * roles specified here will not be allowed to authenticate against James.
058     * If the list is empty, group/role restriction will be disabled.
059     */
060    private final List<String> groupDNs;
061
062    /**
063     * Initialises an instance from the contents of a
064     * <code>&lt;restriction&gt;<code> configuration XML
065     * element.
066     *
067     * @param configuration The avalon configuration instance that encapsulates the
068     *                      contents of the <code>&lt;restriction&gt;<code> XML element.
069     * @throws org.apache.commons.configuration.ConfigurationException
070     *          If an error occurs extracting values from the configuration
071     *          element.
072     */
073    public ReadOnlyLDAPGroupRestriction(HierarchicalConfiguration configuration) {
074        groupDNs = new ArrayList<String>();
075
076        if (configuration != null) {
077            memberAttribute = configuration.getString("[@memberAttribute]");
078
079            if (configuration.getKeys("group").hasNext()) {
080                Collections.addAll(groupDNs, configuration.getStringArray("group"));
081            }
082        }
083    }
084
085    /**
086     * Indicates if group/role-based restriction is enabled for the the
087     * user-store, based on the information encapsulated in the instance.
088     *
089     * @return <code>True</code> If there list of group/role distinguished names
090     *         is not empty, and <code>false</code> otherwise.
091     */
092    protected boolean isActivated() {
093        return !groupDNs.isEmpty();
094    }
095
096    /**
097     * Converts an instance of this type to a string.
098     *
099     * @return A string representation of the instance.
100     */
101    public String toString() {
102        return "Activated=" + isActivated() + "; Groups=" + groupDNs;
103    }
104
105    /**
106     * Returns the distinguished-names (DNs) of all the members of the groups
107     * specified in the restriction list. The information is organised as a list
108     * of <code>&quot;&lt;groupDN&gt;=&lt;
109     * [userDN1,userDN2,...,userDNn]&gt;&quot;</code>. Put differently, each
110     * <code>groupDN</code> is associated to a list of <code>userDNs</code>.
111     *
112     * @return Returns a map of groupDNs to userDN lists.
113     * @throws NamingException Propagated from underlying LDAP communication layer.
114     */
115    protected Map<String, Collection<String>> getGroupMembershipLists(LdapContext ldapContext) throws NamingException {
116        Map<String, Collection<String>> result = new HashMap<String, Collection<String>>();
117
118        for (String groupDN : groupDNs) {
119            result.put(groupDN, extractMembers(ldapContext.getAttributes(groupDN)));
120        }
121
122        return result;
123    }
124
125    /**
126     * Extracts the DNs for members of the group with the given LDAP context
127     * attributes. This is achieved by extracting all the values of the LDAP
128     * attribute, with name equivalent to the field value
129     * {@link #memberAttribute}, from the attributes collection.
130     *
131     * @param groupAttributes The attributes taken from the group's LDAP context.
132     * @return A collection of distinguished-names for the users belonging to
133     *         the group with the specified attributes.
134     * @throws NamingException Propagated from underlying LDAP communication layer.
135     */
136    private Collection<String> extractMembers(Attributes groupAttributes) throws NamingException {
137        Collection<String> result = new ArrayList<String>();
138        Attribute members = groupAttributes.get(memberAttribute);
139        NamingEnumeration<?> memberDNs = members.getAll();
140
141        while (memberDNs.hasMore())
142            result.add(memberDNs.next().toString());
143
144        return result;
145    }
146}