/*
* Sun Public License
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the SLAMD Distributed Load Generation Engine.
* The Initial Developer of the Original Code is Neil A. Wilson.
* Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.realm;
import java.security.MessageDigest;
import java.security.Principal;
import java.util.Enumeration;
import java.util.Hashtable;
import netscape.ldap.LDAPAttribute;
import netscape.ldap.LDAPConnection;
import netscape.ldap.LDAPDN;
import netscape.ldap.LDAPEntry;
import netscape.ldap.LDAPException;
import netscape.ldap.LDAPSearchResults;
import netscape.ldap.LDAPUrl;
import netscape.ldap.factory.JSSESocketFactory;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.catalina.realm.RealmBase;
import com.slamd.asn1.ASN1Element;
import com.slamd.jobs.JSSEBlindTrustSocketFactory;
/**
* This class implements a Tomcat Realm that allows authentication against an
* LDAP directory server. It overcomes many of the limitations of the
* <CODE>JNDIRealm</CODE> implementation provided by default (e.g., this version
* does not require the login ID attribute to be included in the DN), implements
* caching (so that it's not necessary to query the directory every time a new
* page is loaded), and provides support for verifying that the user is a member
* of a specified static group, dynamic group, or role in addition to verifying
* that the credentials are valid.
* <BR><BR>
* The configurable parameters for this realm are:
* <UL>
* <LI>ldapHost -- The address of the directory server in which the user
* entries are stored. This is required to have a value.</LI>
* <LI>ldapPort -- The port number of the directory server in which the user
* entries are stored. This value must be between 1 and 65535.</LI>
* <LI>bindDN -- The DN that should be used to bind to the directory to find
* user entries and make membership determinations. If left blank, then
* those operations will be performed anonymously.</LI>
* <LI>bindPassword -- The password for the bind DN. If left blank, then the
* search and membership determination will be performed anonymously.</LI>
* <LI>userBase -- The DN of the base entry below which user entries exist in
* the directory server. This is required to have a value. If a
* membership DN is specified, then it does not need to be below this
* base.</LI>
* <LI>membershipDN -- The DN of a static group, dynamic group, or role in
* which a user must be a member in order to be authenticated. If this
* is left blank, then no membership check will be performed (i.e., any
* user that is in the directory and provides a valid password will be
* granted access).</LI>
* <LI>loginIDAttribute -- The name of the LDAP attribute that will be queried
* to find user entries based on the value of the login ID provided.</LI>
* <LI>useSSL -- Indicates whether the communication with the LDAP directory
* server will use SSL. The value must be either "true" or "false".</LI>
* <LI>blindTrust -- Indicates whether to blindly trust any SSL certificate
* presented by the directory server. The value must be either "true" or
* "false".</LI>
* <LI>sslKeyStore -- The location of the JSSE key store file to use if SSL is
* enabled.</LI>
* <LI>sslKeyPassword -- The password to use when accessing the JSSE key store
* if SSL is enabled.</LI>
* <LI>sslTrustStore -- The location of the JSSE trust store file to use if
* SSL is enabled.</LI>
* <LI>sslTrustPassword -- The password to use when accessing the JSSE trust
* store if SSL is enabled.</LI>
* </UL>
*
*
* @author Neil A. Wilson
*/
public class LDAPRealm
extends RealmBase
{
/**
* The interval in milliseconds over which the cache needs to be cleaned.
*/
public static final int CACHE_CLEANUP_INTERVAL = 300000;
/**
* The length of time in milliseconds that user information should stay in the
* user cache before being reloaded from the directory.
*/
public static final int CACHE_EXPIRATION_TIME = 1800000;
/**
* The membership type that will be used if the entry for the membership DN
* has not yet been retrieved to determine the type of entry.
*/
public static final int MEMBERSHIP_TYPE_UNKNOWN = -1;
/**
* The membership type that will be used if no membership determination is to
* be made.
*/
public static final int MEMBERSHIP_TYPE_NONE = 0;
/**
* The membership type that will be used if the user should be verified as a
* member of a static group.
*/
public static final int MEMBERSHIP_TYPE_STATIC = 1;
/**
* The membership type that will be used if the user should be verified as a
* member of a dynamic group.
*/
public static final int MEMBERSHIP_TYPE_DYNAMIC = 2;
/**
* The membership type that will be used if the user should be verified as a
* member of a role.
*/
public static final int MEMBERSHIP_TYPE_ROLE = 3;
/**
* The name of the LDAP attribute that holds the LDAP URL used to hold the
* criteria for membership in a dynamic group.
*/
public static final String MEMBER_URL_ATTRIBUTE = "memberURL";
/**
* The name of the LDAP attribute that contains the list of roles for which a
* user is a member.
*/
public static final String ROLE_ATTRIBUTE = "nsRole";
/**
* The system property used to specify the location of the JSSE key store.
*/
public static final String SSL_KEY_STORE_PROPERTY =
"javax.net.ssl.keyStore";
/**
* The system property used to specify the password for the JSSE key store.
*/
public static final String SSL_KEY_PASSWORD_PROPERTY =
"javax.net.ssl.keyStorePassword";
/**
* The system property used to specify the location of the JSSE trust store.
*/
public static final String SSL_TRUST_STORE_PROPERTY =
"javax.net.ssl.trustStore";
/**
* The system property used to specify the password for the JSSE trust store.
*/
public static final String SSL_TRUST_PASSWORD_PROPERTY =
"javax.net.ssl.trustStorePassword";
/**
* The set of attributes to return if only the role is needed from the user's
* entry.
*/
public static final String[] ROLE_ATTRS = new String[] { ROLE_ATTRIBUTE };
/**
* The set of attributes to return if no attributes are needed from the user's
* entry.
*/
public static final String[] NO_ATTRS = new String[] { "1.1" };
/**
* The flag that indicates whether SSL will be used to communicate with the
* directory server.
*/
protected boolean useSSL = false;
/**
* The flag that indicates whether to blindly trust any SSL certificate
* presented by the directory server.
*/
protected boolean blindTrust = true;
/**
* A hashtable containing cached credential information so that we don't have
* to go to the directory server for every request.
*/
protected Hashtable<String,CachedUser> userCache = null;
/**
* The DN that will be used to bind to the user directory to find user
* accounts.
*/
protected String bindDN = null;
/**
* The password for the bind DN.
*/
protected String bindPassword = null;
/**
* The address to use when connecting to the user directory.
*/
protected String ldapHost = null;
/**
* The port number that will be used to contact the user directory.
*/
protected String ldapPort = "389";
/**
* The name of the LDAP attribute that will be used to find user entries based
* on the provided user name.
*/
protected String loginIDAttribute = "uid";
/**
* The DN of a static group, dynamic group, or role in which a user must be a
* member in order to be successfully authenticated.
*/
protected String membershipDN = null;
/**
* The password to use when accessing the JSSE key store.
*/
protected String sslKeyPassword = null;
/**
* The location of the JSSE key store to use for SSL communication with the
* directory.
*/
protected String sslKeyStore = null;
/**
* The password to use when accessing the JSSE trust store.
*/
protected String sslTrustPassword = null;
/**
* The location of the JSSE trust store to use for SSL communication with the
* directory.
*/
protected String sslTrustStore = null;
/**
* The DN under which user entries exist in the user directory.
*/
protected String userBase = null;
// The type of membership that should be checked when performing an
// authentication.
private int membershipType = MEMBERSHIP_TYPE_UNKNOWN;
// The port number to use to connect to the directory server.
private int port = 389;
// The LDAP connection that will be used to bind to the user directory to
// verify user credentials.
private LDAPConnection bindConnection = null;
// The LDAP connection that will be used to find user entries based on the
// provided login ID.
private LDAPConnection searchConnection = null;
// The time at which the next cache cleanup should occur.
private long nextCleanupTime;
// The message digest used to generate SHA-1 hashes.
private MessageDigest shaDigest = null;
// The search base from the LDAP URL that will be used to verify membership in
// a dynamic group.
private String membershipURLBase = null;
// The filter from the LDAP URL that will be used to verify membership in a
// dynamic group.
private String membershipURLFilter = null;
/**
* Retrieves the DN that will be used to connect to the user directory to find
* user entries.
*
* @return The DN that will be used to connect to the user directory to find
* user entries.
*/
public String getBindDN()
{
return bindDN;
}
/**
* Specifies the DN that will be used to connect to the user directory to find
* user entries.
*
* @param bindDN The DN that will be used to connect to the user directory
* to find user entries.
*/
public void setBindDN(String bindDN)
{
this.bindDN = bindDN;
}
/**
* Retrieves the password to use for the bind DN.
*
* @return The password to use for the bind DN.
*/
public String getBindPassword()
{
return bindPassword;
}
/**
* Specifies the password to use for the bind DN.
*
* @param bindPassword THe password to use for the bind DN.
*/
public void setBindPassword(String bindPassword)
{
this.bindPassword = bindPassword;
}
/**
* Retrieves the address to use for the user directory server.
*
* @return The address to use for the user directory server.
*/
public String getLdapHost()
{
return ldapHost;
}
/**
* Specifies the address to use for the user directory server.
*
* @param ldapHost The address to use for the user directory server.
*/
public void setLdapHost(String ldapHost)
{
this.ldapHost = ldapHost;
}
/**
* Retrieves the port number for the user directory server.
*
* @return The port number for the user directory server.
*/
public int getLdapPort()
{
return port;
}
/**
* Specifies the port number for the user directory server.
*
* @param ldapPort The port number for the user directory server.
*/
public void setLdapPort(int ldapPort)
{
this.port = ldapPort;
this.ldapPort = String.valueOf(ldapPort);
}
/**
* Specifies the port number for the user directory server.
*
* @param ldapPort The port number for the user directory server.
*/
public void setLdapPort(String ldapPort)
{
try
{
this.port = Integer.parseInt(ldapPort);
this.ldapPort = ldapPort;
} catch (Exception e) {}
}
/**
* Retrieves the name of the LDAP attribute that will be used to find user
* entries based on the provided user name.
*
* @return The name of the LDAP attribute that will be used to find user
* entries based on the provided user name.
*/
public String getLoginIDAttribute()
{
return loginIDAttribute;
}
/**
* Specifies the name of the LDAP attribute that will be used to find user
* entries based on the provided user name.
*
* @param loginIDAttribute The name of the LDAP attribute that will be used
* to find user entries based on the provided user
* name.
*/
public void setLoginIDAttribute(String loginIDAttribute)
{
this.loginIDAttribute = loginIDAttribute;
}
/**
* Retrieves the location in the user directory under which user entries may
* be found.
*
* @return The location in the user directory under which user entries may be
* found.
*/
public String getUserBase()
{
return userBase;
}
/**
* Specifies the location in the user directory under which user entries may
* be found.
*
* @param userBase The location in the user directory under which user
* entries may be found.
*/
public void setUserBase(String userBase)
{
this.userBase = userBase;
}
/**
* Retrieves the DN of the static group, dynamic group, or role entry for
* which a user must be a member in order to be authenticated.
*
* @return The DN of the static group, dynamic group, or role entry for which
* a user must be a member in order to be authenticated.
*/
public String getMembershipDN()
{
return membershipDN;
}
/**
* Specifies the DN of the static group, dynamic group, or role entry for
* which a user must be a member in order to be authenticated.
*
* @param membershipDN The DN of the static group, dynamic group, or role
* entry for which a user must be a member in order to
* be authenticated.
*/
public void setMembershipDN(String membershipDN)
{
this.membershipDN = membershipDN;
membershipType = MEMBERSHIP_TYPE_UNKNOWN;
}
/**
* Indicates whether SSL will be used to communicate with the directory
* server.
*
* @return <CODE>true</CODE> if SSL will be used to communicate with the
* directory server, or <CODE>false</CODE> if not.
*/
public boolean getUseSSL()
{
return useSSL;
}
/**
* Specifies whether to use SSL to communicate with the directory server. If
* SSL is to be used, then the value specified must be "true".
*
* @param useSSL The string that specifies whether to use SSL to communicate
* with the directory server.
*/
public void setUseSSL(String useSSL)
{
this.useSSL = useSSL.equalsIgnoreCase("true");
}
/**
* Indicates whether to blindly trust any SSL certificate presented by the
* directory server.
*
* @return <CODE>true</CODE> if the certificate should be blindly trusted, or
* <CODE>false</CODE> if not.
*/
public boolean getBlindTrust()
{
return blindTrust;
}
/**
* Specifies whether to blindly trust any SSL certificate presented by the
* directory server.
*
* @param blindTrust The string that specifies whether to blindly trust any
* SSL certificate.
*/
public void setBlindTrust(String blindTrust)
{
this.blindTrust = blindTrust.equalsIgnoreCase("true");
}
/**
* Specifies the location of the JSSE key store that is to be used for SSL
* communication.
*
* @param sslKeyStore The location of the JSSE key store that is to be used
* for SSL communication.
*/
public void setSslKeyStore(String sslKeyStore)
{
this.sslKeyStore = sslKeyStore;
if ((sslKeyStore != null) && (sslKeyStore.length() > 0))
{
System.setProperty(SSL_KEY_STORE_PROPERTY, sslKeyStore);
}
}
/**
* Retrieves the location of the JSSE key store that is to be used for SSL
* communication.
*
* @return The location of the JSSE key store that is to be used for SSL
* communication.
*/
public String getSslKeyStore()
{
return sslKeyStore;
}
/**
* Specifies the password used to access the JSSE key store.
*
* @param sslKeyPassword The password used to access the JSSE key store.
*/
public void setSslKeyPassword(String sslKeyPassword)
{
this.sslKeyPassword = sslKeyPassword;
if ((sslKeyPassword != null) && (sslKeyPassword.length() > 0))
{
System.setProperty(SSL_KEY_PASSWORD_PROPERTY, sslKeyPassword);
}
}
/**
* Retrieves the password that will be used to access the JSSE key store.
*
* @return The password that will be used to access the JSSE key store.
*/
public String getSslKeyPassword()
{
return sslKeyPassword;
}
/**
* Specifies the location of the JSSE trust store that is to be used for SSL
* communication.
*
* @param sslTrustStore The location of the JSSE trust store that is to be
* used for SSL communication.
*/
public void setSslTrustStore(String sslTrustStore)
{
this.sslTrustStore = sslTrustStore;
if ((sslTrustStore != null) && (sslTrustStore.length() > 0))
{
System.setProperty(SSL_TRUST_STORE_PROPERTY, sslTrustStore);
}
}
/**
* Retrieves the location of the JSSE trust store that is to be used for SSL
* communication.
*
* @return The location of the JSSE trust store that is to be used for SSL
* communication.
*/
public String getSslTrustStore()
{
return sslTrustStore;
}
/**
* Specifies the password used to access the JSSE trust store.
*
* @param sslTrustPassword The password used to access the JSSE trust store.
*/
public void setSslTrustPassword(String sslTrustPassword)
{
this.sslTrustPassword = sslTrustPassword;
if ((sslTrustPassword != null) && (sslTrustPassword.length() > 0))
{
System.setProperty(SSL_TRUST_PASSWORD_PROPERTY, sslTrustPassword);
}
}
/**
* Retrieves the password that will be used to access the JSSE trust store.
*
* @return The password that will be used to access the JSSE trust store.
*/
public String getSslTrustPassword()
{
return sslTrustPassword;
}
/**
* Retrieves the Principal associated with the specified username and
* credentials. If no user could be found with the specified username, or if
* the provided credentials are invalid, then <CODE>null</CODE> will be
* returned.
*
* @param username The provided username that will be used to find the
* user entry.
* @param credentials The credentials that will be used to authenticate the
* user.
*
* @return The Principal associated with the specified username and
* credentials.
*/
@Override()
public synchronized Principal authenticate(String username,
String credentials)
{
// First, see if the user cache exists. If not, then create it. If so,
// then see if it needs to be pruned.
long now = System.currentTimeMillis();
if (userCache == null)
{
userCache = new Hashtable<String,CachedUser>();
nextCleanupTime = now + CACHE_CLEANUP_INTERVAL;
}
else
{
if (now > nextCleanupTime)
{
cleanUserCache();
}
}
// First, make sure that the provided credentials are not empty. If they
// are, then we cannot verify the user's identity.
if ((credentials == null) || (credentials.length() == 0))
{
return null;
}
// Generate a SHA-1 hash of the password.
byte[] hashedPassword = hashPassword(credentials);
if (hashedPassword == null)
{
return null;
}
// See if the cache contains information about the user.
CachedUser cachedUser = userCache.get(username);
if (cachedUser != null)
{
if (! byteArraysAreEqual(hashedPassword, cachedUser.getHashedPassword()))
{
return null;
}
else
{
if (now >= cachedUser.getExpirationTime())
{
userCache.remove(username);
}
else
{
return cachedUser.getUserPrincipal();
}
}
}
// The user wasn't in the cache, so perform a search in the directory to
// find the user's entry.
LDAPEntry userEntry = getUserEntry(username);
if (userEntry == null)
{
return null;
}
// Verify the user's credentials.
String userDN = userEntry.getDN();
if (! credentialsAreValid(userDN, credentials))
{
return null;
}
// See if we need to check membership for the user.
if (membershipType == MEMBERSHIP_TYPE_UNKNOWN)
{
determineMembershipType();
}
if (membershipType != MEMBERSHIP_TYPE_NONE)
{
if (! isMember(userEntry))
{
return null;
}
}
// The username and credentials are valid, so return the Principal
// associated with the user.
Principal userPrincipal = new GenericPrincipal(this, username, credentials);
long expirationTime = now + CACHE_EXPIRATION_TIME;
cachedUser = new CachedUser(username, userDN, hashedPassword, userPrincipal,
expirationTime);
userCache.put(username, cachedUser);
return userPrincipal;
}
/**
* Retrieves the DN of the user with the specified username.
*
* @param username The username provided by the user that is to be used to
* find the user's entry.
*
* @return The DN for the user with the specified user name, or
* <CODE>null</CODE> if the user DN could not be determined.
*/
private LDAPEntry getUserEntry(String username)
{
if (searchConnection == null)
{
// Convert the port string to a number.
try
{
port = Integer.parseInt(ldapPort);
if ((port < 1) || (port > 65535))
{
log("The port number must be between 1 and 65535.");
return null;
}
}
catch (NumberFormatException nfe)
{
log("Cannot interpret " + ldapPort + " as an integer value.");
return null;
}
// Establish the connection to use to find user entries.
try
{
if (useSSL)
{
if (blindTrust)
{
JSSEBlindTrustSocketFactory socketFactory =
new JSSEBlindTrustSocketFactory();
searchConnection = new LDAPConnection(socketFactory);
}
else
{
searchConnection = new LDAPConnection(new JSSESocketFactory(null));
}
}
else
{
searchConnection = new LDAPConnection();
}
searchConnection.connect(3, ldapHost, port, bindDN, bindPassword);
}
catch (LDAPException le)
{
log("Could not establish the search connection: " + le);
searchConnection = null;
return null;
}
}
// Perform a search to find the user's entry.
try
{
String filter = '(' + loginIDAttribute + '=' + username + ')';
LDAPSearchResults results =
searchConnection.search(userBase, LDAPConnection.SCOPE_SUB,
filter, ROLE_ATTRS, false);
while (results.hasMoreElements())
{
Object element = results.nextElement();
if (element instanceof LDAPEntry)
{
return (LDAPEntry) element;
}
}
}
catch (LDAPException le)
{
log("Could not perform a search in the user directory: " + le);
try
{
searchConnection.disconnect();
} catch (Exception e) {}
searchConnection = null;
}
// If we have gotten here, then we could not determine the DN.
return null;
}
/**
* Indicates whether the provided credentials are valid for the user with the
* specified DN.
*
* @param userDN The DN of the user for which to perform the bind.
* @param credentials The password to use when binding as the specified
* user.
*
* @return <CODE>true</CODE> if the user's credentials are valid, or
* <CODE>false</CODE> if they are not or if the validity could not be
* verified.
*/
private boolean credentialsAreValid(String userDN, String credentials)
{
if (bindConnection == null)
{
try
{
port = Integer.parseInt(ldapPort);
if ((port < 1) || (port > 65535))
{
log("The port number must be between 1 and 65535.");
return false;
}
}
catch (NumberFormatException nfe)
{
log("Cannot interpret " + ldapPort + " as an integer value.");
return false;
}
try
{
if (useSSL)
{
if (blindTrust)
{
JSSEBlindTrustSocketFactory socketFactory =
new JSSEBlindTrustSocketFactory();
bindConnection = new LDAPConnection(socketFactory);
}
else
{
bindConnection = new LDAPConnection(new JSSESocketFactory(null));
}
}
else
{
bindConnection = new LDAPConnection();
}
bindConnection.connect(ldapHost, port);
}
catch (LDAPException le)
{
log("Could not establish the bind connection: " + le);
bindConnection = null;
return false;
}
}
// Perform a bind as the user to verify the credentials.
try
{
bindConnection.bind(3, userDN, credentials);
return true;
}
catch (LDAPException le)
{
switch (le.getLDAPResultCode())
{
case LDAPException.NO_SUCH_OBJECT:
case LDAPException.INVALID_CREDENTIALS:
case LDAPException.INAPPROPRIATE_AUTHENTICATION:
case LDAPException.CONSTRAINT_VIOLATION:
break;
default:
log("Could not perform the bind: " + le);
try
{
bindConnection.disconnect();
} catch (Exception e) {}
bindConnection = null;
}
return false;
}
}
/**
* Generates a SHA-1 hash of the provided password. The password will not be
* salted, but storing hashed passwords in memory is still preferable to
* storing them in the clear.
*
* @param credentials The password provided by the end user.
*
* @return The hashed version of the password, or <CODE>null</CODE> if a
* problem occurs.
*/
private byte[] hashPassword(String credentials)
{
if (shaDigest == null)
{
try
{
shaDigest = MessageDigest.getInstance("SHA");
}
catch (Exception e)
{
log("Unable to obtain SHA digest implementation.");
return null;
}
}
return shaDigest.digest(ASN1Element.getBytes(credentials));
}
/**
* Determines whether the contents of the two provided byte arrays are
* identical.
*
* @param b1 The first byte array for which to make the determination.
* @param b2 The second byte array for which to make the determination.
*
* @return <CODE>true</CODE> if the contents of the provided byte arrays are
* equal, or <CODE>false</CODE> if not.
*/
private static boolean byteArraysAreEqual(byte[] b1, byte[] b2)
{
if ((b1 == null) || (b2 == null) || (b1.length != b2.length))
{
return false;
}
for (int i=0; i < b1.length; i++)
{
if (b1[i] != b2[i])
{
return false;
}
}
return true;
}
/**
* Determines the type of entry that is specified in the membership DN. That
* is, it determines whether the membership DN refers to a static group, a
* dynamic group, or a role.
*/
private void determineMembershipType()
{
// If no membership DN was provided, then we don't need to check any kind
// of membership.
if ((membershipDN == null) || (membershipDN.length() == 0))
{
membershipType = MEMBERSHIP_TYPE_NONE;
return;
}
// If a DN was provided, then retrieve the entry associated with it.
String[] attrsToReturn = new String[]
{
"objectClass",
MEMBER_URL_ATTRIBUTE
};
LDAPEntry membershipEntry = null;
try
{
membershipEntry = searchConnection.read(membershipDN, attrsToReturn);
}
catch (LDAPException le)
{
log("Unable to retrieve membership entry " + membershipDN + ": " +
le);
membershipType = MEMBERSHIP_TYPE_UNKNOWN;
return;
}
// Retrieve the objectClass values from the entry.
LDAPAttribute attr = membershipEntry.getAttribute("objectClass");
if (attr == null)
{
log("Unable to retrieve objectClass values from entry " + membershipDN);
membershipType = MEMBERSHIP_TYPE_UNKNOWN;
return;
}
String[] values = attr.getStringValueArray();
if ((values == null) || (values.length == 0))
{
log("Unable to retrieve objectClass values from entry " + membershipDN);
membershipType = MEMBERSHIP_TYPE_UNKNOWN;
return;
}
// Look at the objectClass values to try to determine the entry type.
membershipType = MEMBERSHIP_TYPE_ROLE;
for (int i=0; i < values.length; i++)
{
String lowerValue = values[i].toLowerCase();
if (lowerValue.equals("groupofnames") ||
lowerValue.equals("groupofuniquenames"))
{
// It is a static group. We don't need to do anything else, so return.
membershipType = MEMBERSHIP_TYPE_STATIC;
return;
}
else if (lowerValue.equals("groupofurls"))
{
// It is a dynamic group. Get the member URL and extract the search
// base and filter from it.
attr = membershipEntry.getAttribute(MEMBER_URL_ATTRIBUTE);
if (attr == null)
{
log("Unable to retrieve " + MEMBER_URL_ATTRIBUTE +
" attribute from groupOfURLs entry " + membershipDN);
membershipType = MEMBERSHIP_TYPE_UNKNOWN;
return;
}
values = attr.getStringValueArray();
if ((values == null) || (values.length == 0))
{
log("Unable to retrieve " + MEMBER_URL_ATTRIBUTE +
" attribute from groupOfURLs entry " + membershipDN);
membershipType = MEMBERSHIP_TYPE_UNKNOWN;
return;
}
try
{
LDAPUrl url = new LDAPUrl(values[1]);
membershipURLBase = url.getDN();
membershipURLFilter = url.getFilter();
membershipType = MEMBERSHIP_TYPE_DYNAMIC;
return;
}
catch (Exception e)
{
log("Unable to parse value '" + values[1] + "' as an LDAP URL.");
membershipType = MEMBERSHIP_TYPE_UNKNOWN;
return;
}
}
}
}
/**
* Determines whether the provided user is a member of the membership DN.
*
* @param userEntry The user entry for which to make the determination.
*
* @return <CODE>true</CODE> if it is determined that the user is a member,
* or <CODE>false</CODE> if the membership could not be determined.
*/
private boolean isMember(LDAPEntry userEntry)
{
switch (membershipType)
{
case MEMBERSHIP_TYPE_STATIC:
String userDN = userEntry.getDN();
String filter = "(|(&(objectclass=groupOfNames)(member=" + userDN +
"))(&(objectClass=groupOfUniqueNames)(uniqueMember=" +
userDN + ")))";
try
{
LDAPSearchResults results =
searchConnection.search(membershipDN, LDAPConnection.SCOPE_BASE,
filter, NO_ATTRS, false);
while (results.hasMoreElements())
{
Object element = results.nextElement();
if (element instanceof LDAPEntry)
{
return true;
}
}
return false;
}
catch (Exception e)
{
return false;
}
case MEMBERSHIP_TYPE_DYNAMIC:
userDN = LDAPDN.normalize(userEntry.getDN());
if (! userDN.endsWith(membershipURLBase))
{
return false;
}
try
{
LDAPSearchResults results =
searchConnection.search(userDN, LDAPConnection.SCOPE_BASE,
membershipURLFilter, NO_ATTRS, false);
while (results.hasMoreElements())
{
Object element = results.nextElement();
if (element instanceof LDAPEntry)
{
return true;
}
}
return false;
}
catch (Exception e)
{
return false;
}
case MEMBERSHIP_TYPE_ROLE:
LDAPAttribute roleAttr = userEntry.getAttribute(ROLE_ATTRIBUTE);
String[] roleValues = null;
if ((roleAttr == null) ||
((roleValues = roleAttr.getStringValueArray()) == null) ||
(roleValues.length == 0))
{
return false;
}
for (int i=0; i < roleValues.length; i++)
{
String roleDN = LDAPDN.normalize(roleValues[i]);
if (roleDN.equals(membershipDN))
{
return true;
}
}
return false;
}
return false;
}
/**
* This method iterates through the user cache and removes all cached user
* information that has expired.
*/
private void cleanUserCache()
{
long now = System.currentTimeMillis();
Enumeration keys = userCache.keys();
while (keys.hasMoreElements())
{
String key = (String) keys.nextElement();
CachedUser user = userCache.get(key);
if (user.getExpirationTime() < now)
{
userCache.remove(key);
}
}
nextCleanupTime = now + CACHE_CLEANUP_INTERVAL;
}
/**
* Shuts down this realm and releases the resources associated with it.
*/
public void stop()
{
// Invoke the superclass stop() method.
try
{
super.stop();
} catch (Exception e) {}
// Close the search connection.
try
{
searchConnection.disconnect();
} catch (LDAPException le) {}
// Close the bind connection.
try
{
bindConnection.disconnect();
} catch (LDAPException le) {}
}
/**
* Retrieves a short name for this Realm implementation, for use in log
* messages.
*
* @return A short name for this Realm implementation, for use in log
* messages.
*/
@Override()
protected String getName()
{
return "LDAPRealm";
}
/**
* Retrieves the password associated with the given principal's user name.
*
* @param username The name of the user for which to retrieve the password.
*
* @return The password associated with the given principal's user name.
*/
@Override()
protected String getPassword(String username)
{
return null;
}
/**
* Retrieves the Principal associated with the given user name.
*
* @param username The name of the user for which to retrieve the Principal.
*
* @return The Principal associated with the given user name.
*/
@Override()
protected Principal getPrincipal(String username)
{
return null;
}
}