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><users-store></code> configuration child-element
041 * <code><restriction><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><restriction><code> configuration XML
066 * element.
067 *
068 * @param configuration
069 * The avalon configuration instance that encapsulates the
070 * contents of the <code><restriction><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>"<groupDN>=<
117 * [userDN1,userDN2,...,userDNn]>"</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 }