FileDocCategorySizeDatePackage
LDAPRealm.javaAPI DocGlassfish v2 API26137Fri Jul 27 09:58:40 BST 2007com.sun.enterprise.security.auth.realm.ldap

LDAPRealm

public final class LDAPRealm extends com.sun.enterprise.security.auth.realm.IASRealm
Realm wrapper for supporting LDAP authentication.

See LDAPLoginModule documentation for more details on the operation of the LDAP realm and login module.

The ldap realm needs the following properties in its configuration:

  • directory - URL of LDAP directory to use
  • base-dn - The base DN to use for user searches.
  • jaas-ctx - JAAS context name used to access LoginModule for authentication.

Besides JDK Context properties start with java.naming, javax.security, one can also set connection pool related properties starting with com.sun.jndi.ldap.connect.pool. See http://java.sun.com/products/jndi/tutorial/ldap/connect/config.html for details. Also, the following optional attributes can also be specified:

  • search-filter - LDAP filter to use for searching for the user entry based on username given to iAS. The default value is uid=%s where %s is expanded to the username.
  • group-base-dn - The base DN to use for group searches. By default its value is the same as base-dn.
  • group-search-filter - The LDAP filter to use for searching group membership of a given user. The default value is uniquemember=%d where %d is expanded to the DN of the user found by the user search.
  • group-target - The attribute which value(s) are interpreted as group membership names of the user. Default value is cn.
  • search-bind-dn - The dn of ldap user. optional and no default value.
  • search-bind-password - The password of search-bind-dn.optional and no default value.
  • pool-size - The JNDI ldap connection pool size.
see
com.sun.enterprise.security.auth.login.LDAPLoginModule

Fields Summary
public static final String
AUTH_TYPE
public static final String
PARAM_DIRURL
public static final String
PARAM_USERDN
public static final String
PARAM_SEARCH_FILTER
public static final String
PARAM_GRPDN
public static final String
PARAM_GRP_SEARCH_FILTER
public static final String
PARAM_GRP_TARGET
public static final String
PARAM_MODE
public static final String
PARAM_JNDICF
public static final String
PARAM_POOLSIZE
public static final String
PARAM_BINDDN
public static final String
PARAM_BINDPWD
public static final String
MODE_FIND_BIND
public static final String
SUBST_SUBJECT_NAME
public static final String
SUBST_SUBJECT_DN
private static final String
SEARCH_FILTER_DEFAULT
private static final String
GRP_SEARCH_FILTER_DEFAULT
private static final String
GRP_TARGET_DEFAULT
private static final String
MODE_DEFAULT
private static final String
JNDICF_DEFAULT
private static final int
POOLSIZE_DEFAULT
private final String[]
_dnOnly
private static final String
SUN_JNDI_POOL
private static final String
SUN_JNDI_POOL_
private static final String
SUN_JNDI_POOL_PROTOCOL
private static final String
SUN_JNDI_POOL_MAXSIZE
private static final String
DYNAMIC_GROUP_OBJECT_FACTORY
public static final String
DYNAMIC_GROUP_FACTORY_OBJECT_PROPERTY
private static final String
DYNAMIC_GROUP_STATE_FACTORY
public static final String
DYNAMIC_GROUP_STATE_FACTORY_PROPERTY
public static final String
LDAP_SOCKET_FACTORY
public static final String
DEFAULT_SSL_LDAP_SOCKET_FACTORY
public static final String
LDAPS_URL
public static final String
DEFAULT_POOL_PROTOCOL
public static final String
DYNAMIC_GROUP_FILTER
public static final String
SSL
private HashMap
groupCache
private Vector
emptyVector
private Properties
ldapBindProps
Constructors Summary
Methods Summary
private booleanbindAsUser(java.lang.String bindDN, java.lang.String password)
Attempt to bind as a specific DN.

        boolean bindSuccessful=false;

        Properties p = getLdapBindProps();
        
        p.put(Context.SECURITY_PRINCIPAL, bindDN);
        p.put(Context.SECURITY_CREDENTIALS, password);
        
        DirContext ctx = null;
        try {
            ctx = new InitialDirContext(p);
            bindSuccessful = true;
        } catch (Exception e) {
            if (_logger.isLoggable(Level.FINEST)) {
                _logger.finest("Error binding to directory as: " + bindDN);
                _logger.finest("Exception from JNDI: " + e.toString());
            }
        } finally {
            if (ctx != null) {
                try {
                    ctx.close();
                } catch (NamingException e) {};
            }
        }
        return bindSuccessful;
    
private java.util.ListdynamicGroupSearch(javax.naming.directory.DirContext ctx, java.lang.String baseDN, java.lang.String target, java.lang.String userDN)
Search for group membership using the given connection.

        
        List groupList = new ArrayList();
        String filter = DYNAMIC_GROUP_FILTER;
        
        String[] targets = new String[] { target, "memberUrl" };

        try {
            SearchControls ctls = new SearchControls();
            ctls.setReturningAttributes(targets);
            ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            ctls.setReturningObjFlag(true);
            
            NamingEnumeration e = ctx.search(baseDN, filter, ctls);
            
            while(e.hasMore()) {
                SearchResult res = (SearchResult)e.next();
                Object searchedObject = res.getObject();
                if (searchedObject instanceof GroupOfURLs){ // dynamic group
                    GroupOfURLs gurls = (GroupOfURLs) searchedObject;
                    Principal x500principal = new X500Principal(userDN);
                    if (gurls.isMember(x500principal)) {
                        
                        Attribute grpAttr = res.getAttributes().get(target);
                        int sz = grpAttr.size();
                        for (int i=0; i<sz; i++) {
                            String s = (String)grpAttr.get(i);
                            groupList.add(s);
                        }
                    }
                }
                // recommended by Jaya Hangal from JDK team
                if (searchedObject instanceof Context) {
                    ((Context)searchedObject).close();
                }
            }
        } catch (Exception e) {
            _logger.log(Level.WARNING, "ldaplm.searcherror", filter);
            _logger.log(Level.WARNING, "security.exception", e);
        }
        return groupList;
    
public java.lang.String[]findAndBind(java.lang.String _username, java.lang.String _password)
Supports mode=find-bind. See class documentation.

        // do search for user, substituting %s for username
        StringBuffer sb = new StringBuffer(getProperty(PARAM_SEARCH_FILTER));
        substitute(sb, SUBST_SUBJECT_NAME, _username);
        String userid = sb.toString();

        // attempt to bind as the user
        DirContext ctx = null;
        String srcFilter = null;
        String[] grpList = null;
        try {
            ctx = new InitialDirContext(getLdapBindProps());
            String realUserDN = userSearch(ctx, getProperty(PARAM_USERDN), userid);
            if (realUserDN == null) {
                String msg = sm.getString("ldaprealm.usernotfound", _username);
                throw new LoginException(msg);
            }

            boolean bindSuccessful = bindAsUser(realUserDN, _password);
            if (bindSuccessful == false) {
                String msg = sm.getString("ldaprealm.bindfailed", realUserDN);
                throw new LoginException(msg);
            }

            // search groups using above connection, substituting %d (and %s)
            sb = new StringBuffer(getProperty(PARAM_GRP_SEARCH_FILTER));
            substitute(sb, SUBST_SUBJECT_NAME, _username);
            substitute(sb, SUBST_SUBJECT_DN, realUserDN);

            srcFilter = sb.toString();
            ArrayList groupsList = new ArrayList();
            groupsList.addAll(groupSearch(ctx, getProperty(PARAM_GRPDN), srcFilter, getProperty(PARAM_GRP_TARGET)));
            // search filter is constructed internally as
            // as a groupofURLS
            groupsList.addAll(dynamicGroupSearch(ctx, getProperty(PARAM_GRPDN), getProperty(PARAM_GRP_TARGET),
                realUserDN));          
            grpList = new String[groupsList.size()];
            groupsList.toArray(grpList);
        } catch (Exception e) {
            throw new LoginException(e.toString() );
        } finally {
            if (ctx != null) {
                try {
                    ctx.close();
                } catch (NamingException e) {};
            }
        }

        if (_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE, "LDAP:Group search filter: " + srcFilter);
            StringBuffer gb = new StringBuffer();
            gb.append("Group memberships found: ");
            if (grpList != null) {
                for (int i=0; i<grpList.length; i++) {
                    gb.append(" "+grpList[i]);
                }
            } else {
                gb.append("(null)");
            }
            if (_logger.isLoggable(Level.FINE)) {
                _logger.log(Level.FINE, "LDAP: "+ gb.toString());
            }
        }
        grpList = addAssignGroups(grpList);
        setGroupNames(_username, grpList);

        if(_logger.isLoggable(Level.FINE)){
             _logger.log(Level.FINE, "LDAP: login succeeded for: " + _username);
        }

        return grpList;
    
public java.lang.StringgetAuthType()
Returns a short (preferably less than fifteen characters) description of the kind of authentication which is supported by this realm.

return
Description of the kind of authentication that is directly supported by this realm.

        return AUTH_TYPE;
    
public java.util.EnumerationgetGroupNames(java.lang.String username)
Returns the name of all the groups that this user belongs to. Note that this information is only known after the user has logged in. This is called from web path role verification, though it should not be.

param
username Name of the user in this realm whose group listing is needed.
return
Enumeration of group names (strings).
exception
InvalidOperationException thrown if the realm does not support this operation - e.g. Certificate realm does not support this operation.

        Vector v = (Vector)groupCache.get(username);
        if (v == null) {
            if (_logger.isLoggable(Level.FINE)) {
                _logger.log(Level.FINE, "No groups available for: "+username);
            }
            // we don't load group here as we need to bind ctx to user with
            // password before doing that and password is not available here
            return emptyVector.elements();
        } else {
            return v.elements();
        }
    
private java.util.PropertiesgetLdapBindProps()
Get binding properties defined in server.xml for LDAP server.

        return (Properties)ldapBindProps.clone();
    
private java.util.ListgroupSearch(javax.naming.directory.DirContext ctx, java.lang.String baseDN, java.lang.String filter, java.lang.String target)
Search for group membership using the given connection.

        
        List groupList = new ArrayList();
        
        try {
            String[] targets = new String[1];
            targets[0] = target;
            
            SearchControls ctls = new SearchControls();
            ctls.setReturningAttributes(targets);
            ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            
            NamingEnumeration e = ctx.search(baseDN, filter, ctls);
            
            while(e.hasMore()) {
                SearchResult res = (SearchResult)e.next();
                Attribute grpAttr = res.getAttributes().get(target);
                int sz = grpAttr.size();
                for (int i=0; i<sz; i++) {
                    String s = (String)grpAttr.get(i);
                    groupList.add(s);
                }
            }
                
        } catch (Exception e) {
            _logger.log(Level.WARNING, "ldaprealm.searcherror", filter);
            _logger.log(Level.WARNING, "security.exception", e);
        }

        return groupList;
    
public synchronized voidinit(java.util.Properties props)
Initialize a realm with some properties. This can be used when instantiating realms from their descriptions. This method may only be called a single time.

param
props Initialization parameters used by this realm.
exception
BadRealmException If the configuration parameters identify a corrupt realm.
exception
NoSuchRealmException If the configuration parameters specify a realm which doesn't exist.


                                                                            
        
          
    
        super.init(props);
        String url = props.getProperty(PARAM_DIRURL);
        this.setProperty(PARAM_DIRURL, url);
        ldapBindProps.setProperty(Context.PROVIDER_URL, url);
        
        String dn = props.getProperty(PARAM_USERDN);
        this.setProperty(PARAM_USERDN, dn);

        String jaasCtx = props.getProperty(IASRealm.JAAS_CONTEXT_PARAM);
        this.setProperty(IASRealm.JAAS_CONTEXT_PARAM, jaasCtx);
        
        if (url==null || dn==null || jaasCtx==null) {
            String msg = sm.getString("ldaprealm.badconfig", url, dn, jaasCtx);
            throw new BadRealmException(msg);
        }

        String mode = props.getProperty(PARAM_MODE, MODE_DEFAULT);
        if (!MODE_DEFAULT.equals(mode)) {
            String msg = sm.getString("ldaprealm.badmode", mode);
            throw new BadRealmException(msg);
        }
        this.setProperty(PARAM_MODE, mode);

        String ctxF = props.getProperty(PARAM_JNDICF, JNDICF_DEFAULT);
        this.setProperty(PARAM_JNDICF, ctxF);
        ldapBindProps.setProperty(Context.INITIAL_CONTEXT_FACTORY, ctxF);

        String searchFilter = props.getProperty(
                PARAM_SEARCH_FILTER, SEARCH_FILTER_DEFAULT);
        this.setProperty(PARAM_SEARCH_FILTER,searchFilter);

        String grpDN = props.getProperty(PARAM_GRPDN, dn);
        this.setProperty(PARAM_GRPDN, grpDN);

        String grpSearchFilter = props.getProperty(
                PARAM_GRP_SEARCH_FILTER, GRP_SEARCH_FILTER_DEFAULT);
        this.setProperty(PARAM_GRP_SEARCH_FILTER, grpSearchFilter);

        String grpTarget = props.getProperty(
                PARAM_GRP_TARGET, GRP_TARGET_DEFAULT);
        this.setProperty(PARAM_GRP_TARGET, grpTarget);

        String objectFactory = props.getProperty(
                DYNAMIC_GROUP_FACTORY_OBJECT_PROPERTY, DYNAMIC_GROUP_OBJECT_FACTORY);
        this.setProperty(DYNAMIC_GROUP_FACTORY_OBJECT_PROPERTY, objectFactory);
        ldapBindProps.setProperty(DYNAMIC_GROUP_FACTORY_OBJECT_PROPERTY, objectFactory);

        String stateFactory = props.getProperty(
                DYNAMIC_GROUP_STATE_FACTORY_PROPERTY, DYNAMIC_GROUP_STATE_FACTORY);
        this.setProperty(DYNAMIC_GROUP_STATE_FACTORY_PROPERTY, stateFactory);
        ldapBindProps.setProperty(DYNAMIC_GROUP_STATE_FACTORY_PROPERTY, stateFactory);

        String bindDN = props.getProperty(PARAM_BINDDN);
        if (bindDN != null) {
            this.setProperty(PARAM_BINDDN, bindDN);
            ldapBindProps.setProperty(Context.SECURITY_PRINCIPAL, bindDN);
        } 
        String bindPWD = props.getProperty(PARAM_BINDPWD);
        if (bindPWD != null) {
            this.setProperty(PARAM_BINDPWD, bindPWD);
            ldapBindProps.setProperty(Context.SECURITY_CREDENTIALS, bindPWD);
        }

        Enumeration penum = props.propertyNames();
        while (penum.hasMoreElements()) {
            String propName = (String)penum.nextElement();
            if (propName.startsWith("java.naming.") ||
                    propName.startsWith("javax.security.")) {
                ldapBindProps.setProperty(propName, props.getProperty(propName));
            } else if (propName.startsWith(SUN_JNDI_POOL_) &&
                    !SUN_JNDI_POOL_MAXSIZE.equals(propName)) {
                if (System.getProperty(propName) == null) {
                    System.setProperty(propName, props.getProperty(propName));
                }
            }
        }

        String poolSize =
            Integer.getInteger(PARAM_POOLSIZE,POOLSIZE_DEFAULT).toString();
        String sunPoolSizeStr = props.getProperty(SUN_JNDI_POOL_MAXSIZE,
            poolSize);
        //Precedence rule: SUN_JNDI_POOL_MAXSIZE > PARAM_POOLSIZE > POOLSIZE_DEFAULT
        try {
            sunPoolSizeStr = Integer.valueOf(sunPoolSizeStr).toString();
        } catch(Exception ex) {
            sunPoolSizeStr = poolSize;
        }
        if (System.getProperty(SUN_JNDI_POOL_MAXSIZE) == null) {       
            System.setProperty(SUN_JNDI_POOL_MAXSIZE, sunPoolSizeStr);
        }
        this.setProperty(PARAM_POOLSIZE, sunPoolSizeStr);

        String usePool = props.getProperty(SUN_JNDI_POOL, "true");
        ldapBindProps.setProperty(SUN_JNDI_POOL, usePool);
        
        if(url != null && url.startsWith(LDAPS_URL)) {
            ldapBindProps.setProperty(LDAP_SOCKET_FACTORY,
                    DEFAULT_SSL_LDAP_SOCKET_FACTORY);
            if (System.getProperty(SUN_JNDI_POOL_PROTOCOL) == null) {
                System.setProperty(SUN_JNDI_POOL_PROTOCOL,DEFAULT_POOL_PROTOCOL);
            }
            if (_logger.isLoggable(Level.FINE)) {
                _logger.log(Level.FINE, "LDAPRealm : Using custom socket factory for SSL with pooling");
            }
        }
        
        if (_logger.isLoggable(Level.FINE)) {
            Properties tempProps = (Properties)ldapBindProps.clone();
            tempProps.remove(Context.SECURITY_CREDENTIALS);
            _logger.log(Level.FINE, "LDAPRealm : " + tempProps);
        }

        groupCache = new HashMap();
        emptyVector = new Vector();
    
private voidsetGroupNames(java.lang.String username, java.lang.String[] groups)
Set group membership info for a user.

See bugs 4646133,4646270 on why this is here.

        Vector v = new Vector(groups.length);
        for (int i=0; i<groups.length; i++) {
            v.add(groups[i]);
        }
        groupCache.put(username, v);
    
private static voidsubstitute(java.lang.StringBuffer sb, java.lang.String target, java.lang.String value)
Do string substitution. target is replaced by value for all occurences.

        int i = sb.indexOf(target);
        while (i >= 0) {
            sb.replace(i, i+target.length(), value);
            i = sb.indexOf(target);
        }
    
private java.lang.StringuserSearch(javax.naming.directory.DirContext ctx, java.lang.String baseDN, java.lang.String filter)
Do anonymous search for the user. Should be unique if exists.

        if (_logger.isLoggable(Level.FINEST)) {
            _logger.log(Level.FINE, "search: baseDN: "+ baseDN +
                           "  filter: " + filter);
        }
            
        String foundDN = null;
        NamingEnumeration namingEnum = null;

        SearchControls ctls = new SearchControls();
        ctls.setReturningAttributes(_dnOnly);
        ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        ctls.setCountLimit(1);

        try {
            namingEnum = ctx.search(baseDN, filter, ctls);
            if (namingEnum.hasMore()) {
                SearchResult res = (SearchResult)namingEnum.next();

                StringBuffer sb = new StringBuffer();
                //for dn name with '/'
                CompositeName compDN = new CompositeName(res.getName());
                String ldapDN = compDN.get(0);
                sb.append(ldapDN);
                
                if (res.isRelative()) {
                    sb.append(",");
                    sb.append(baseDN);
                }
                foundDN = sb.toString();
                if (_logger.isLoggable(Level.FINEST)) {
                    _logger.log(Level.FINE, "Found user DN: " + foundDN);
                }
            }
        } catch (Exception e) {
            _logger.log(Level.WARNING, "ldaprealm.searcherror", filter);
            _logger.log(Level.WARNING, "security.exception", e);
        } finally {
            if (namingEnum != null) {
                try {
                    namingEnum.close();
                } catch(Exception ex) {
                }
            }
        }

        return foundDN;