Author: norman
Date: Sat Jan 23 11:13:30 2010
New Revision: 902385

URL: http://svn.apache.org/viewvc?rev=902385&view=rev
Log:
Add ReadOnlyUsersLdapRepository, contributed by Obi Ezechukwu. Thx again 
(JAMES-934)

Added:
    
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyLDAPGroupRestriction.java
    
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyLDAPUser.java
    
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyUsersLDAPRepository.java
    
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/SimpleLDAPConnection.java

Added: 
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyLDAPGroupRestriction.java
URL: 
http://svn.apache.org/viewvc/james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyLDAPGroupRestriction.java?rev=902385&view=auto
==============================================================================
--- 
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyLDAPGroupRestriction.java
 (added)
+++ 
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyLDAPGroupRestriction.java
 Sat Jan 23 11:13:30 2010
@@ -0,0 +1,178 @@
+/****************************************************************
+ * 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.userrepository;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+
+import org.apache.commons.configuration.HierarchicalConfiguration;
+
+/**
+ * <p>
+ * Encapsulates the information required to restrict users to LDAP groups or 
roles.
+ * Instances of this type are populated from the contents of the 
<code>&lt;users-store&gt;</code>
+ * configuration child-element <code>&lt;restriction&gt;<code>.  
+ * </p>
+ *   
+ * @see ReadOnlyUsersLDAPRepository
+ * @see ReadOnlyLDAPUser
+ * 
+ * @author Obi Ezechukwu
+ */
+
+public class ReadOnlyLDAPGroupRestriction
+{   
+       /**
+        * <p>
+        * The name of the LDAP attribute name which holds the unique names 
+        * (distinguished-names/DNs) of the members of the group/role.
+        * 
+        * </p>  
+        */
+    private String memberAttribute;
+    
+    /**
+     * <p>
+     * The distinguished-names of the LDAP groups/roles to which James users 
must 
+     * belong. A user who is not a member of at least one of the groups or 
roles 
+     * specified here will not be allowed to authenticate against James. If 
the 
+     * list is empty, group/role restriction  will be disabled.
+     * </p>
+     */
+    private List<String> groupDNs; 
+    
+    
+    /**
+     * <p>
+     * Initialises an instance from the contents of 
+     * a <code>&lt;restriction&gt;<code> configuration XML 
+     * element. 
+     * </p> 
+     * @param configuration    The avalon configuration instance that 
+     * encapsulates the contents of the <code>&lt;restriction&gt;<code>
+     * XML element.
+     * 
+     * @throws ConfigurationException  If an error occurs extracting
+     * values from the configuration element.
+     */
+    @SuppressWarnings("unchecked")
+    public ReadOnlyLDAPGroupRestriction(HierarchicalConfiguration 
configuration) {
+        groupDNs = new ArrayList<String>();
+
+               if (configuration != null) {
+                       memberAttribute = 
configuration.getString("[...@memberattribute]");
+
+                       if (configuration.getKeys("group").hasNext()) {
+                       List<String> groupNames = configuration
+                                       .getList("group");
+
+                               for (int i = 0; i < groupNames.size(); i++) {
+                                       groupDNs.add(groupNames.get(i));
+                               }
+                       }
+               }
+       }
+
+    /**
+     * <p>
+     * Indicates if group/role-based restriction is enabled for the
+     * the user-store, based on the information encapsulated in the instance.
+     * </p>
+     * @return <code>True</code> If there list of group/role distinguished 
+     * names is not empty, and <code>false</code> otherwise.
+     */
+       protected boolean isActivated() {
+               return !groupDNs.isEmpty();
+       }
+
+       /**
+        * <p>
+        * Converts an instance of this type to a string.
+        * </p>
+        * @return A string representation of the instance. 
+        */
+       public String toString() {
+               return "Activated=" + isActivated() + "; Groups=" + groupDNs;
+       }
+
+       /**
+        * <p>
+        * Returns the distinguished-names (DNs) of all the members of the 
+        * groups specified in the restriction list. The information is 
+        * organised as a list of <code>&quot;&lt;groupDN&gt;=&lt;
+        * [userDN1,userDN2,...,userDNn]&gt;&quot;</code>. Put differently,
+        * each <code>groupDN</code> is associated to a list of 
<code>userDNs</code>.   
+        * </p>
+        * 
+        * @param connection    The connection to the LDAP directory server.
+        * @return      Returns a map of groupDNs to userDN lists.
+        * @throws NamingException      Propagated from underlying 
+        * LDAP communication layer. 
+        */
+       protected Map<String,Collection<String>> 
getGroupMembershipLists(SimpleLDAPConnection connection)
+                       throws NamingException {
+               Map<String,Collection<String>> result = new 
HashMap<String,Collection<String>>();
+
+               Iterator<String> groupDNsIterator = groupDNs.iterator();
+
+               Attributes groupAttributes;
+               while (groupDNsIterator.hasNext()) {
+                       String groupDN = (String) groupDNsIterator.next();
+                       groupAttributes = connection.getLdapContext()
+                                       .getAttributes(groupDN);
+                       result.put(groupDN, extractMembers(groupAttributes));
+               }
+
+               return result;
+       }
+
+       /**
+        * <p>
+        * Extracts the DNs for members of the group with the given 
+        * LDAP context attributes. This is achieved by extracting all the 
values
+        * of the LDAP attribute, with name equivalent to the field value
+        * {...@link #memberAttribute}, from the attributes collection.
+        * </p>
+        * 
+        * @param groupAttributes       The attributes taken from the group's 
LDAP context.
+        * @return      A collection of distinguished-names for the users 
belonging to 
+        * the group with the specified attributes.
+        * @throws NamingException      Propagated from underlying LDAP 
communication layer.
+        */
+       private Collection<String> extractMembers(Attributes groupAttributes)
+                       throws NamingException {
+               Collection<String> result = new ArrayList<String>();
+               Attribute members = groupAttributes.get(memberAttribute);
+               NamingEnumeration<?> memberDNs = members.getAll();
+
+               while (memberDNs.hasMore())
+                       result.add(memberDNs.next().toString());
+
+               return result;
+       }
+}

Added: 
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyLDAPUser.java
URL: 
http://svn.apache.org/viewvc/james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyLDAPUser.java?rev=902385&view=auto
==============================================================================
--- 
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyLDAPUser.java
 (added)
+++ 
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyLDAPUser.java
 Sat Jan 23 11:13:30 2010
@@ -0,0 +1,153 @@
+/****************************************************************
+ * 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.userrepository;
+
+import java.io.Serializable;
+
+import javax.naming.NamingException;
+
+import org.apache.james.api.user.User;
+
+/**
+ * <p>
+ * Encapsulates the details of a user as taken from 
+ * an LDAP compliant directory. Instances of this class
+ * are only applicable to the {...@link ReadOnlyUsersLDAPRepository}
+ * or its subclasses. Consequently it does not permit the mutation 
+ * of user details. It is intended purely as an encapsulation 
+ * of the user information as held in the LDAP directory, and as a means
+ * of authenticating the user against the LDAP server. Consequently 
+ * invocations of the contract method {...@link User#setPassword(String)}
+ * always returns <code>false</code>. 
+ * </p>
+ * 
+ * @see SimpleLDAPConnection
+ * @see ReadOnlyUsersLDAPRepository
+ * 
+ */
+public class ReadOnlyLDAPUser implements User, Serializable {
+       private static final long serialVersionUID = -6712066073820393235L;
+    
+    /**
+     * <p>
+     * The user's identifier or name. This is the value 
+     * that is returned by the method {...@link User#getUserName()}.
+     * It is also from this value that the user's email 
+     * address is formed, so for example: if the value of
+     * this field is <code>&quot;john.bold&quot;</code>, and the 
+     * domain is <code>&quot;myorg.com&quot;</code>, the user's email
+     * address will be <code>&quot;john.bold&#64;myorg.com&quot;</code>.
+     * </p> 
+     */
+    private String userName;
+    
+    
+    /**
+     * <p>
+     * The distinguished name of the user-record in the 
+     * LDAP directory.
+     * </p> 
+     */
+    private String userDN;
+    
+    /**
+     * <p>
+     * The URL for connecting to the LDAP server from which to 
+     * retrieve the user's details.
+     * </p> 
+     */
+    private String ldapURL;    
+    
+    /**
+     * <p>
+     * Constructs an instance for the given user-details, 
+     * and which will authenticate against the given host.
+     * </p> 
+     * 
+     * @param userName The user-identifier/name. This is the 
+     * value with which the field {...@link #userName} will be initialised, 
+     * and which will be returned by invoking {...@link #getUserName()}.
+     * @param userDN   The distinguished (unique-key) of the user details
+     * as stored on the LDAP directory.
+     * @param ldapURL  The URL of the LDAP server on which the user 
+     * details are held. This is also the host against which the 
+     * user will be authenticated, when {...@link #verifyPassword(String)} 
+     * is invoked. 
+     */
+    public ReadOnlyLDAPUser(String userName, String userDN, String ldapURL) {
+               this.userName = userName;
+               this.userDN = userDN;
+               this.ldapURL = ldapURL;
+       }
+
+    /**
+     * <p>
+     * Fulfils the contract {...@link User#getUserName()}. It returns the value
+     * of the field {...@link #userName}. This is generally the value from 
+     * which the user email address is built, by appending the domain name 
+     * to it.
+     * </p>  
+     * 
+     * @return The user's identifier or name.
+     */
+    public String getUserName() {
+               return userName;
+       }
+
+    /**
+     * <p>
+     * Implementation of contract {...@link User#setPassword(String)}, which 
is 
+     * provided for compliance purposes only. Instances of this type 
+     * mirror LDAP data and do not perform any updates to the directory. 
+     * Consequently, this method always returns <code>false</code> and does 
not do 
+     * any work.
+     * </p>
+     * @return <code>False</code>
+     */
+    public boolean setPassword(String newPass) {
+               return false;
+       }
+
+
+    /**
+     * <p>
+     * Verifies that the password supplied is actually the user's password,
+     * by attempting to bind to the LDAP server using the user's username 
+     * and the supplied password.
+     * </p>
+     * @param password The password to validate.
+     * @return <code>True</code> if a connection can successfully be 
established 
+     * to the LDAP host using the user's id and the supplied password, and 
<code>False</code>
+     * otherwise. <b>Please note</b> that if the LDAP server has suffered a 
crash or failure 
+     * in between the initialisation of the user repository and the invocation 
of this method,  
+     * the result will still be <code>false</code>.
+     */
+    public boolean verifyPassword(String password) {
+               boolean result;
+               try {
+                       SimpleLDAPConnection.openLDAPConnection(userDN, 
password, ldapURL);
+                       result = true;
+               } catch (NamingException exception) {
+                       result = false;
+               }
+               return result;
+       }
+
+}

Added: 
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyUsersLDAPRepository.java
URL: 
http://svn.apache.org/viewvc/james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyUsersLDAPRepository.java?rev=902385&view=auto
==============================================================================
--- 
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyUsersLDAPRepository.java
 (added)
+++ 
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/ReadOnlyUsersLDAPRepository.java
 Sat Jan 23 11:13:30 2010
@@ -0,0 +1,591 @@
+/****************************************************************
+ * 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.userrepository;
+
+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.NameClassPair;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.logging.Log;
+import org.apache.james.api.user.User;
+import org.apache.james.api.user.UsersRepository;
+import org.apache.james.lifecycle.Configurable;
+import org.apache.james.lifecycle.LogEnabled;
+
+/**
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * Based on this assumption, this repository is strictly <b>read-only</b>. As a
+ * consequence, user modification, deletion and creation requests will be
+ * ignored when using this repository.
+ * </p>
+ * <p>
+ * The following fragment of XML provides an example configuration to enable
+ * this repository: </br>
+ * 
+ * <pre>
+ *  &lt;users-store&gt;
+ *      &lt;repository name=&quot;LDAPUsers&quot; 
+ *      
class=&quot;org.apache.james.userrepository.ReadOnlyUsersLDAPRepository&quot; 
+ *      ldapHost=&quot;ldap://myldapserver:389&quot;
+ *      principal=&quot;uid=ldapUser,ou=system&quot;
+ *      credentials=&quot;password&quot;
+ *      userBase=&quot;ou=People,o=myorg.com,ou=system&quot;
+ *      userIdAttribute=&quot;uid&quot;/&gt;
+ *  &lt;/users-store&gt;
+ * </pre>
+ * 
+ * </br>
+ * 
+ * Its constituent attributes are defined as follows:
+ * <ul>
+ * <li><b>ldapHost:</b> The URL of the LDAP server to connect to.</li>
+ * <li>
+ * <b>principal:</b> The name (DN) of the user with which to initially bind to
+ * the LDAP server.</li>
+ * <li>
+ * <b>credentials:</b> The password with which to initially bind to the LDAP
+ * server.</li>
+ * <li>
+ * <b>userBase:</b>The context within which to search for user entities.</li>
+ * <li>
+ * <b>userIdAttribute:</b>The name of the LDAP attribute which holds user ids.
+ * For example &quot;uid&quot; for Apache DS, or &quot;sAMAccountName&quot; for
+ * Microsoft Active Directory.</li>
+ * </ul>
+ * </p>
+ * 
+ * <p>
+ * In order to enable group/role based access restrictions, you can use the
+ * &quot;&lt;restriction&gt;&quot; configuration element. An example of this is
+ * shown below: <br>
+ * 
+ * <pre>
+ * &lt;restriction
+ *     memberAttribute=&quot;uniqueMember&quot;&gt;
+ *             
&lt;group&gt;cn=PermanentStaff,ou=Groups,o=myorg.co.uk,ou=system&lt;/group&gt;
+ *             
&lt;group&gt;cn=TemporaryStaff,ou=Groups,o=myorg.co.uk,ou=system&lt;/group&gt;
+ * &lt;/restriction&gt;
+ *</pre>
+ * 
+ * <br>
+ * Its constituent attributes and elements are defined as follows:
+ * <ul>
+ * <li>
+ * <b>memberAttribute:</b> The LDAP attribute whose values indicate the DNs of
+ * the users which belong to the group or role.</li>
+ * <li>
+ * <b>group:</b> 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 &quot;&lt;restriction&gt;&quot; sections.</li>
+ * </ul>
+ * </p>
+ * 
+ * @see SimpleLDAPConnection
+ * @see ReadOnlyLDAPUser
+ * @see ReadOnlyLDAPGroupRestriction
+ * 
+ * @author Obi Ezechukwu
+ */
+public class ReadOnlyUsersLDAPRepository implements UsersRepository, 
Configurable, LogEnabled {
+
+    /**
+     * <p>
+     * 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 &quot;dn&quot; and &quot;credentials&quot;.The
+     * value of this field is taken from the value of the configuration
+     * attribute &quot;ldapHost&quot;.
+     * </p>
+     */
+    private String ldapHost;
+
+    /**
+     * <p>
+     * The value of this field is taken from the configuration attribute
+     * &quot;userIdAttribute&quot;. This is the LDAP attribute type which holds
+     * the userId value. Note that this is not the same as the email address
+     * attribute.
+     * </p>
+     */
+    private String userIdAttribute;
+
+    /**
+     * <p>
+     * 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 &quot;userBase&quot;.
+     * </p>
+     */
+    private String userBase;
+
+    /**
+     * <p>
+     * The user with which to initially bind to the LDAP server. The value of
+     * this field is taken from the configuration attribute
+     * &quot;principal&quot;.
+     * </p>
+     */
+    private String principal;
+
+    /**
+     * <p>
+     * The password/credentials with which to initially bind to the LDAP 
server.
+     * The value of this field is taken from the configuration attribute
+     * &quot;credentials&quot;.
+     * </p>
+     */
+    private String credentials;
+
+    /**
+     * <p>
+     * Encapsulates the information required to restrict users to LDAP groups 
or
+     * roles. This object is populated from the contents of the configuration
+     * element &lt;restriction&gt;.
+     * </p>
+     */
+    private ReadOnlyLDAPGroupRestriction restriction;
+
+    /**
+     * <p>
+     * The connection handle to the LDAP server. This is the connection that is
+     * built from the configuration attributes &quot;ldapHost&quot;,
+     * &quot;principal&quot; and &quot;credentials&quot;.
+     * </p>
+     */
+    private SimpleLDAPConnection ldapConnection;
+
+    private Log log;
+
+    /**
+     * <p>
+     * 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}.
+     * </p>
+     * 
+     * @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]");
+
+        restriction = new 
ReadOnlyLDAPGroupRestriction(configuration.configurationAt("restriction"));
+
+    }
+
+    /**
+     * <p>
+     * Initialises the user-repository instance. It will create a connection to
+     * the LDAP host using the supplied configuration.
+     * </p>
+     * 
+     * @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());
+        }
+    }
+
+    /**
+     * <p>
+     * Indicates if the user with the specified DN can be found in the group
+     * membership map&#45;as encapsulated by the specified parameter map.
+     * </p>
+     * 
+     * @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
+     * 
+     *            
<code>&quot;&lt;groupDN&gt;=&lt;[userDN1,userDN2,...,userDNn]&gt;&quot;</code>
+     *            pairs. In essence, each <code>groupDN</code> string is
+     *            associated to a list of <code>userDNs</code>.
+     * @return <code>True</code> if the specified userDN is associated with at
+     *         least one group in the parameter map, and <code>False</code>
+     *         otherwise.
+     */
+    private boolean userInGroupsMembershipList(String userDN, Map<String, 
Collection<String>> groupMembershipList) {
+        boolean result = false;
+
+        Collection<Collection<String>> memberLists = 
groupMembershipList.values();
+        Iterator<Collection<String>> memberListsIterator = 
memberLists.iterator();
+
+        while (memberListsIterator.hasNext() && !result) {
+            Collection<String> groupMembers = memberListsIterator.next();
+            result = groupMembers.contains(userDN);
+        }
+
+        return result;
+    }
+
+    /**
+     * <p>
+     * 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}.
+     * </p>
+     * 
+     * @return A set containing all the relevant users found in the LDAP
+     *         directory.
+     * @throws NamingException
+     *             Propagated from the LDAP communication layer.
+     */
+    private Set<String> getAllUsersFromLDAP() throws NamingException {
+        Set<String> result = new HashSet<String>();
+        NamingEnumeration<?> boundNames = 
ldapConnection.getLdapContext().list(userBase);
+
+        NameClassPair elementInfo;
+        while (boundNames.hasMore()) {
+            elementInfo = (NameClassPair) boundNames.next();
+            result.add(elementInfo.getNameInNamespace());
+        }
+
+        return result;
+    }
+
+    /**
+     * <p>
+     * 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)}.
+     * </p>
+     * 
+     * @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<ReadOnlyLDAPUser> 
buildUserCollection(Collection<String> userDNs) throws NamingException {
+        List<ReadOnlyLDAPUser> results = new ArrayList<ReadOnlyLDAPUser>();
+
+        Iterator<String> userDNIterator = userDNs.iterator();
+
+        while (userDNIterator.hasNext()) {
+            ReadOnlyLDAPUser user = buildUser(userDNIterator.next());
+            results.add(user);
+        }
+
+        return results;
+    }
+
+    /**
+     * <p>
+     * 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}.
+     * </p>
+     * 
+     * @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<ReadOnlyLDAPUser> 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<ReadOnlyLDAPUser> 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<String> list() {
+        List<String> result = new ArrayList<String>();
+        try {
+
+            Iterator<ReadOnlyLDAPUser> 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<String> getValidUsers() throws NamingException {
+        Set<String> userDNs = getAllUsersFromLDAP();
+        Collection<String> validUserDNs;
+
+        if (restriction.isActivated()) {
+            Map<String, Collection<String>> groupMembershipList = 
restriction.getGroupMembershipLists(ldapConnection);
+            validUserDNs = new ArrayList<String>();
+
+            Iterator<String> 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;
+    }
+
+}

Added: 
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/SimpleLDAPConnection.java
URL: 
http://svn.apache.org/viewvc/james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/SimpleLDAPConnection.java?rev=902385&view=auto
==============================================================================
--- 
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/SimpleLDAPConnection.java
 (added)
+++ 
james/server/trunk/avalon-user-function/src/main/java/org/apache/james/userrepository/SimpleLDAPConnection.java
 Sat Jan 23 11:13:30 2010
@@ -0,0 +1,141 @@
+/****************************************************************
+ * 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.userrepository;
+
+import java.util.Properties;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+
+/**
+ * <p>
+ * A connection handle to an LDAP server which is created by means 
+ * of a simple principal and password/credentials 
+ * authentication against the LDAP server. 
+ * </p>
+ * 
+ * @author Obi Ezechukwu
+ */
+public class SimpleLDAPConnection {
+
+       /**
+        * <p>
+        * The distinguished-name/DN of the principal to authenticate
+        * against the LDAP server.
+        * </p>
+        */
+       private String principal;
+       
+       /**
+        * <p>
+        * The credentials with which to authenticate against the LDAP server.
+        * </p>
+        */
+       private String credentials;
+       
+       /**
+        * <p>The URL of the LDAP server.</p>
+        */
+       private String ldapURL;
+       
+       /**
+        * <p>
+        * The root directory context that is visible to the 
+        * authenticated user. 
+        * </p>
+        */
+       private DirContext ldapContext;
+       
+       /**
+        * <p>
+        * Creates an instance with the given login details.
+        * </p>
+        * @param principal             The distinguished-name (DN) of the user
+        * to authenticate against the server.
+        * @param credentials   The credentials with which to authenticate 
+        * the user e.g. the user's password.
+        * @param ldapURL               The URL of the LDAP server against 
which to 
+        * authenticate the user. 
+        */
+       private SimpleLDAPConnection(String principal, String credentials,
+                       String ldapURL) {
+               super();
+               this.principal = principal;
+               this.credentials = credentials;
+               this.ldapURL = ldapURL;
+       }       
+       
+       /**
+        * <p>
+        * Opens a connection to the specified LDAP server using 
+        * the specified user details.  
+        * </p>
+        * @param principal             The distinguished-name (DN) of the user
+        * to authenticate against the server.
+        * @param credentials   The credentials with which to authenticate 
+        * the user e.g. the user's password.
+        * @param ldapURL               The URL of the LDAP server against 
which to 
+        * authenticate the user. 
+        * @return      A connection to the LDAP host.
+        * @throws NamingException      Propagated from the underlying LDAP 
connection.
+        */
+       public static SimpleLDAPConnection openLDAPConnection(String principal,
+                       String credentials, String ldapURL) throws 
NamingException {
+               SimpleLDAPConnection result = new 
SimpleLDAPConnection(principal,
+                               credentials, ldapURL);
+               result.initializeContext();
+               return result;
+       }
+
+       /**
+        * <p>
+        * Returns the root directory context that is visible to the 
+        * authenticated user.
+        * </p>  
+        * @return      The directory context that is visible to the 
+        * authenticated user. 
+        */
+       public DirContext getLdapContext() {
+               return ldapContext;
+       }
+ 
+       /**
+        * <p>
+        * Internal helper method which creates an LDAP/JNDI context using the 
+        * specified user credentials.  
+        * </p>
+        * @throws NamingException      Propagated from underlying 
+        * LDAP communication API.
+        */
+       private void initializeContext() throws NamingException {
+               Properties props = new Properties();
+               props.put(Context.INITIAL_CONTEXT_FACTORY,
+                               "com.sun.jndi.ldap.LdapCtxFactory");
+               props.put(Context.PROVIDER_URL, ldapURL);
+
+               props.put(Context.SECURITY_AUTHENTICATION, "simple");
+               props.put(Context.SECURITY_PRINCIPAL, principal);
+               props.put(Context.SECURITY_CREDENTIALS, credentials);
+
+               ldapContext = new InitialDirContext(props);
+       }
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to