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     ****************************************************************/
019    package org.apache.james.user.ldap;
020    
021    import java.util.ArrayList;
022    import java.util.Collection;
023    import java.util.HashMap;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Map;
027    
028    import javax.naming.NamingEnumeration;
029    import javax.naming.NamingException;
030    import javax.naming.directory.Attribute;
031    import javax.naming.directory.Attributes;
032    import javax.naming.ldap.LdapContext;
033    
034    import org.apache.commons.configuration.HierarchicalConfiguration;
035    
036    /**
037     * <p>
038     * Encapsulates the information required to restrict users to LDAP groups or
039     * roles. Instances of this type are populated from the contents of the
040     * <code>&lt;users-store&gt;</code> configuration child-element
041     * <code>&lt;restriction&gt;<code>.
042     * </p>
043     * 
044     * @see ReadOnlyUsersLDAPRepository
045     * @see ReadOnlyLDAPUser
046     */
047    
048    public class ReadOnlyLDAPGroupRestriction {
049        /**
050         * The name of the LDAP attribute name which holds the unique names
051         * (distinguished-names/DNs) of the members of the group/role.
052         */
053        private String memberAttribute;
054    
055        /**
056         * The distinguished-names of the LDAP groups/roles to which James users
057         * must belong. A user who is not a member of at least one of the groups or
058         * roles specified here will not be allowed to authenticate against James.
059         * If the list is empty, group/role restriction will be disabled.
060         */
061        private List<String> groupDNs;
062    
063        /**
064         * Initialises an instance from the contents of a
065         * <code>&lt;restriction&gt;<code> configuration XML 
066         * element.
067         * 
068         * @param configuration
069         *            The avalon configuration instance that encapsulates the
070         *            contents of the <code>&lt;restriction&gt;<code> XML element.
071         * 
072         * @throws ConfigurationException
073         *             If an error occurs extracting values from the configuration
074         *             element.
075         */
076        @SuppressWarnings("unchecked")
077        public ReadOnlyLDAPGroupRestriction(HierarchicalConfiguration configuration) {
078            groupDNs = new ArrayList<String>();
079    
080            if (configuration != null) {
081                memberAttribute = configuration.getString("[@memberAttribute]");
082    
083                if (configuration.getKeys("group").hasNext()) {
084                    List<String> groupNames = configuration.getList("group");
085    
086                    for (int i = 0; i < groupNames.size(); i++) {
087                        groupDNs.add(groupNames.get(i));
088                    }
089                }
090            }
091        }
092    
093        /**
094         * Indicates if group/role-based restriction is enabled for the the
095         * user-store, based on the information encapsulated in the instance.
096         * 
097         * @return <code>True</code> If there list of group/role distinguished names
098         *         is not empty, and <code>false</code> otherwise.
099         */
100        protected boolean isActivated() {
101            return !groupDNs.isEmpty();
102        }
103    
104        /**
105         * Converts an instance of this type to a string.
106         * 
107         * @return A string representation of the instance.
108         */
109        public String toString() {
110            return "Activated=" + isActivated() + "; Groups=" + groupDNs;
111        }
112    
113        /**
114         * Returns the distinguished-names (DNs) of all the members of the groups
115         * specified in the restriction list. The information is organised as a list
116         * of <code>&quot;&lt;groupDN&gt;=&lt;
117         * [userDN1,userDN2,...,userDNn]&gt;&quot;</code>. Put differently, each
118         * <code>groupDN</code> is associated to a list of <code>userDNs</code>.
119         * 
120         * @param connection
121         *            The connection to the LDAP directory server.
122         * @return Returns a map of groupDNs to userDN lists.
123         * @throws NamingException
124         *             Propagated from underlying LDAP communication layer.
125         */
126        protected Map<String, Collection<String>> getGroupMembershipLists(LdapContext ldapContext) throws NamingException {
127            Map<String, Collection<String>> result = new HashMap<String, Collection<String>>();
128    
129            Iterator<String> groupDNsIterator = groupDNs.iterator();
130    
131            Attributes groupAttributes;
132            while (groupDNsIterator.hasNext()) {
133                String groupDN = (String) groupDNsIterator.next();
134                groupAttributes = ldapContext.getAttributes(groupDN);
135                result.put(groupDN, extractMembers(groupAttributes));
136            }
137    
138            return result;
139        }
140    
141        /**
142         * Extracts the DNs for members of the group with the given LDAP context
143         * attributes. This is achieved by extracting all the values of the LDAP
144         * attribute, with name equivalent to the field value
145         * {@link #memberAttribute}, from the attributes collection.
146         * 
147         * @param groupAttributes
148         *            The attributes taken from the group's LDAP context.
149         * @return A collection of distinguished-names for the users belonging to
150         *         the group with the specified attributes.
151         * @throws NamingException
152         *             Propagated from underlying LDAP communication layer.
153         */
154        private Collection<String> extractMembers(Attributes groupAttributes) throws NamingException {
155            Collection<String> result = new ArrayList<String>();
156            Attribute members = groupAttributes.get(memberAttribute);
157            NamingEnumeration<?> memberDNs = members.getAll();
158    
159            while (memberDNs.hasMore())
160                result.add(memberDNs.next().toString());
161    
162            return result;
163        }
164    }