/* Copyright (c) 2001-2019, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb.auth;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.StartTlsRequest;
import javax.naming.ldap.StartTlsResponse;

import org.hsqldb.lib.FrameworkLogger;

Authenticates to a HyperSQL catalog according to entries in a LDAP database. If using LDAP StartTLS and your server has a certificate not trusted by default by your JRE, then set system property 'javax.net.ssl.trustStore' to the path to a trust store containing the cert (as well as any other certs that your app needs for other purposes).

This class with authenticate login attempts against LDAP entries with RDN of the HyperSQL account name (the precise attribute name defaults to 'uid', but you may change that).

This class purposefully does not support LDAPS, because LDAPS is deprecated in favor of StartTLS, which we do support. If you need to support LDAPS and are using SE 1.6, use our JaasAuthBean with Sun's LdapLoginModule.

This class does not support SASL/External authentication, because the work involved with securely obtaining user-specific certs would be more complex than everything else here combined. Another AuthFunctionBean would have to be written if SASL/External is needed.

To use instances of this class, you must use at least the methods setLdapHost, setParentDn, initialize, plus rolesSchemaAttribute and/or accessAttribute.

For a user to be given HyperSQL catalog access, that user must either have a value for accessAttribute if that property is set (optionally requiring a match with accessValuePattern); or, if the accessAttribute is not set then must have some (any) value for rolesSchemaAttribute (optionally requiring a match with roleSchemaValuePattern). Consequently, if you have set both accessAttribute and rolesSchemaAttribute, the latter attribute will only be consulted if the check of the former attribute succeeds.

If you want roles assigned according to the local HyperSQL database instead of according to LDAP, then set accessAttribute but not rolesSchemaAttribute.

If what is wanted is to grant access but with no roles (overriding local roles if there are any), then set both accessAttribute and rolesSchemaAttribute, but do not set any rolesSchemaAttribute attribute values for these no-role users. (I hesitate to mention it, but you could accomplish the same thing with only a rolesSchemaAttribute attribute, by setting only a dummy role/schema value for non-role users, because HyperSQL will ignore unknown roles or schemas but still give access since a list was still supplied).

Author:Blaine Simpson (blaine dot simpson at admc dot com)
See Also:
Since:2.5.0
/** * Authenticates to a HyperSQL catalog according to entries in a LDAP * database. * If using LDAP StartTLS and your server has a certificate not trusted by * default by your JRE, then set system property 'javax.net.ssl.trustStore' to * the path to a trust store containing the cert (as well as any other certs * that your app needs for other purposes). * <P> * This class with authenticate login attempts against LDAP entries with RDN of * the HyperSQL account name (the precise attribute name defaults to 'uid', but * you may change that). * </P> <P> * This class purposefully does not support LDAPS, because LDAPS is deprecated * in favor of StartTLS, which we do support. * If you need to support LDAPS and are using SE 1.6, use our JaasAuthBean with * Sun's LdapLoginModule. * </P> <P> * This class does not support SASL/External authentication, because the work * involved with securely obtaining user-specific certs would be more complex * than everything else here combined. * Another AuthFunctionBean would have to be written if SASL/External is needed. * </P> <P> * To use instances of this class, you must use at least the methods * setLdapHost, setParentDn, initialize, plus * rolesSchemaAttribute and/or accessAttribute. * </P> <P> * For a user to be given HyperSQL catalog access, that user must either have * a value for accessAttribute if that property is set (optionally requiring * a match with accessValuePattern); or, if the accessAttribute is not set then * must have some (any) value for rolesSchemaAttribute (optionally requiring a * match with roleSchemaValuePattern). * Consequently, if you have set both accessAttribute and rolesSchemaAttribute, * the latter attribute will only be consulted if the check of the former * attribute succeeds. * </P> <P> * If you want roles assigned according to the local HyperSQL database instead * of according to LDAP, then set accessAttribute but not rolesSchemaAttribute. * </P> <P> * If what is wanted is to grant access but with no roles (overriding local * roles if there are any), then set both accessAttribute and * rolesSchemaAttribute, but do not set any rolesSchemaAttribute attribute * values for these no-role users. * (I hesitate to mention it, but you could accomplish the same thing with only * a rolesSchemaAttribute attribute, by setting only a dummy role/schema value * for non-role users, because HyperSQL will ignore unknown roles or schemas * but still give access since a list was still supplied). * </P> * * @see AuthFunctionBean * @see #setLdapHost(String) * @see #setParentDn(String) * @see #init() * @author Blaine Simpson (blaine dot simpson at admc dot com) * @since 2.5.0 */
public class LdapAuthBean implements AuthFunctionBean { private static FrameworkLogger logger = FrameworkLogger.getLog(LdapAuthBean.class); private Integer ldapPort; private String ldapHost, principalTemplate, saslRealm, parentDn; private Pattern roleSchemaValuePattern, accessValuePattern; private String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory"; private boolean tls; // This is for StartTLS, not tunneled TLS/LDAPS. // Variable named just "tls" only for brevity. private String mechanism = "SIMPLE"; private String rdnAttribute = "uid"; private boolean initialized; private String rolesSchemaAttribute, accessAttribute; protected String[] attributeUnion; public LdapAuthBean() { // Intentionally empty }
If this is set, then the entire (brief) transaction with the LDAP server will be encrypted.
Params:
  • isTls – boolean
/** * If this is set, then the entire (brief) transaction with the LDAP server * will be encrypted. * * @param isTls boolean */
public void setStartTls(boolean isTls) { this.tls = isTls; } public void setLdapPort(int ldapPort) { this.ldapPort = Integer.valueOf(ldapPort); }
Throws:
  • IllegalStateException – if any required setting has not been set.
/** * @throws IllegalStateException if any required setting has not been set. */
public void init() { if (ldapHost == null) { throw new IllegalStateException( "Required property 'ldapHost' not set"); } if (parentDn == null) { throw new IllegalStateException( "Required property 'parentDn' not set"); } if (initialContextFactory == null) { throw new IllegalStateException( "Required property 'initialContextFactory' not set"); } if (mechanism == null) { throw new IllegalStateException( "Required property 'mechanism' not set"); } if (rdnAttribute == null) { throw new IllegalStateException( "Required property 'rdnAttribute' not set"); } if (rolesSchemaAttribute == null && accessAttribute == null) { throw new IllegalStateException( "You must set property 'rolesSchemaAttribute' " + "and/or property 'accessAttribute'"); } if (roleSchemaValuePattern != null && rolesSchemaAttribute == null) { throw new IllegalStateException( "If property 'roleSchemaValuePattern' is set, then you " + "must also set property 'rolesSchemaAttribute' to " + "indicate which attribute to evaluate"); } if (accessValuePattern != null && accessAttribute == null) { throw new IllegalStateException( "If property 'accessValuePattern' is set, then you " + "must also set property 'accessAttribute' to " + "indicate which attribute to evaluate"); } if (rolesSchemaAttribute != null && accessAttribute != null) { attributeUnion = new String[] { rolesSchemaAttribute, accessAttribute }; } else if (rolesSchemaAttribute != null) { attributeUnion = new String[] { rolesSchemaAttribute }; } else { attributeUnion = new String[] { accessAttribute }; } initialized = true; }
Assign a pattern to detect honored accessAttribute values. If you set accessAttribute but not accessValuePattern, then all that will be checked for access is if the RDN + parentDN entry has the accessAttribute attribute. (I.e. the specific value will not matter whatsoever).

You may only use this property if you have set property accessAttribute. If you have set accessAttribute but not this property, then access will be decided based solely upon existence of this attribute.

Capture groups in the pattern will be ignored and serve no purpose.

N.b. this Pattern will be used for the matches() operation, therefore it must match the entire candidate value strings (this is different than the find operation which does not need to satisfy the entire candidate value).

Example1 :

 TRUE 
This will match true values per OpenLDAP's boolean OID.
Params:
  • accessValuePattern – Pattern
See Also:
  • matches.matches()
/** * Assign a pattern to detect honored accessAttribute values. If you set * accessAttribute but not accessValuePattern, then all that will be checked * for access is if the RDN + parentDN entry has the accessAttribute * attribute. (I.e. the specific value will not matter whatsoever). * * <P> You may only use this property if you have set property * accessAttribute. If you have set accessAttribute but not this property, * then access will be decided based solely upon existence of this * attribute. * * <P> Capture groups in the pattern will be ignored and serve no purpose. * * * <P> N.b. this Pattern will be used for the matches() operation, therefore * it must match the entire candidate value strings (this is different than * the find operation which does not need to satisfy the entire candidate * value). * * <P>Example1 :<PRE><CODE> TRUE </CODE></PRE> This will match true values * per OpenLDAP's boolean OID. * * @see Matcher#matches() * @param accessValuePattern Pattern */
public void setAccessValuePattern(Pattern accessValuePattern) { this.accessValuePattern = accessValuePattern; }
String wrapper for method setAccessValuePattern(Pattern) Use the (x?) Pattern constructs to set options.
Params:
  • patternString – String
See Also:
  • setAccessValuePattern(Pattern)
/** * String wrapper for method setAccessValuePattern(Pattern) Use the (x?) * Pattern constructs to set options. * * @see #setAccessValuePattern(Pattern) * @param patternString String */
public void setAccessValuePatternString(String patternString) { setAccessValuePattern(Pattern.compile(patternString)); }
Assign a pattern to both detect honored values, and to map from a single value of "rolesSchemaAttribute"s to a HyperSQL role or schema string. If your rolesSchemaAttribute holds only the String values precisely as HyperSQL needs them, then don't use this method at all and all matching attribute values will be passed directly.

You may only use this property if you have set property rolesSchemaAttribute. If rolesSchemaAttribute is set but this property is not set, then the value will directly determine the user's roles and schema.

Unlike the rolesSchemaAttribute, the property at-hand uses the singular for "role", because whereas rolesSchemaAttribute is the attribute for listing multiple roles, roleSchemaValuePattern is used to evaluate single role values.

These are two distinct and important purposes for the specified Pattern.

  1. Values that do not successfully match the pattern will be ignored.
  2. Optionally uses parentheses to specify a single capture group (if you use parentheses to specify more than one matching group, we will only capture for the first). What is captured by this group is exactly the role or schema that HyperSQL will attempt to assign. If no capture parens are given then the Pattern is only used for the acceptance decision, and the LDAP-provided value will be returned verbatim.

Together, these two features work great to extract just the needed role and schema names from 'memberof' DNs, and will have no problem if you also use 'memberof' for unrelated purposes.

N.b. this Pattern will be used for the matches() operation, therefore it must match the entire candidate value strings (this is different than the find operation which does not need to satisfy the entire candidate value).

Example1 :


    cn=([^,]+),ou=dbRole,dc=admc,dc=com
will extract the CN value from matching attribute values.

Example1 :


    cn=[^,]+,ou=dbRole,dc=admc,dc=com
will return the entire cn...com string for matching attribute values.
Params:
  • roleSchemaValuePattern – pattern
See Also:
  • matches.matches()
/** * Assign a pattern to both detect honored values, and to map from a single * value of "rolesSchemaAttribute"s to a HyperSQL role or schema string. * If your rolesSchemaAttribute holds only the String values precisely as * HyperSQL needs them, then don't use this method at all and all matching * attribute values will be passed directly. * <P> * You may only use this property if you have set property * rolesSchemaAttribute. * If rolesSchemaAttribute is set but this property is not set, then * the value will directly determine the user's roles and schema. * <P> * <B>Unlike the rolesSchemaAttribute, the property at-hand uses the * singular for "role", because whereas rolesSchemaAttribute is the * attribute for listing multiple roles, roleSchemaValuePattern is used * to evaluate single role values.</B> * <P> * These are two distinct and important purposes for the specified Pattern. * <OL> * <LI> * Values that do not successfully match the pattern will be ignored. * <LI> * Optionally uses parentheses to specify a single capture group * (if you use parentheses to specify more than one matching group, we * will only capture for the first). * What is captured by this group is exactly the role or schema that * HyperSQL will attempt to assign. * If no capture parens are given then the Pattern is only used for the * acceptance decision, and the LDAP-provided value will be returned * verbatim. * </OL> * * <P> * Together, these two features work great to extract just the needed role * and schema names from 'memberof' DNs, and will have no problem if you * also use 'memberof' for unrelated purposes. * * <P> * N.b. this Pattern will be used for the matches() operation, therefore it * must match the entire candidate value strings (this is different than * the find operation which does not need to satisfy the entire candidate * value). * * <P>Example1 :<PRE><CODE> * cn=([^,]+),ou=dbRole,dc=admc,dc=com * </CODE></PRE> * will extract the CN value from matching attribute values. * * <P>Example1 :<PRE><CODE> * cn=[^,]+,ou=dbRole,dc=admc,dc=com * </CODE></PRE> * will return the entire <CODE>cn...com</CODE> string for matching * attribute values. * * @see Matcher#matches() * @param roleSchemaValuePattern pattern */
public void setRoleSchemaValuePattern(Pattern roleSchemaValuePattern) { this.roleSchemaValuePattern = roleSchemaValuePattern; }
String wrapper for method setRoleSchemaValuePattern(Pattern) Use the (x?) Pattern constructs to set options.
Params:
  • patternString – pattern
Throws:
  • PatternSyntaxException – exception
See Also:
/** * String wrapper for method setRoleSchemaValuePattern(Pattern) * * Use the (x?) Pattern constructs to set options. * * @throws java.util.regex.PatternSyntaxException exception * @see #setRoleSchemaValuePattern(Pattern) * * @param patternString pattern */
public void setRoleSchemaValuePatternString(String patternString) { setRoleSchemaValuePattern(Pattern.compile(patternString)); }
Defaults to "SIMPLE".
Params:
  • mechanism – Either 'SIMPLE' (the default) for LDAP Simple, or one of the LDAP SASL mechanisms, such as 'DIGEST-MD5'.
/** * Defaults to "SIMPLE". * * @param mechanism Either 'SIMPLE' (the default) for LDAP Simple, or * one of the LDAP SASL mechanisms, such as 'DIGEST-MD5'. */
public void setSecurityMechanism(String mechanism) { this.mechanism = mechanism; }
Do not specify URL scheme ("ldap:") because that is implied. (Since we purposefully don't support LDAPS, there would be no reason to change that).

If using StartTLS, then this host name must match the cn of the LDAP server's certificate.

If you need to support LDAPS and are using SE 1.6, use our JaasAuthBean with Sun's LdapLoginModule instead of this class.

Params:
  • ldapHost – host
See Also:
  • JaasAuthBean
/** * Do not specify URL scheme ("ldap:") because that is implied. * (Since we purposefully don't support LDAPS, there would be no reason to * change that). * <P> * If using StartTLS, then this host name must match the cn of the LDAP * server's certificate. * </P> <P> * If you need to support LDAPS and are using SE 1.6, use our JaasAuthBean * with Sun's LdapLoginModule instead of this class. * </P> * * @see JaasAuthBean * @param ldapHost host */
public void setLdapHost(String ldapHost) { this.ldapHost = ldapHost; }
A template String containing place-holder token '${username}'. All occurrences of '${username}' (without the quotes) will be translated to the username that authentication is being attempted with.

If you supply a principalTemplate that does not contain '${username}', then authentication will be user-independent.

It is common to authenticate to LDAP servers with the DN of the user's LDAP entry. In this situation, set principalTemplate to <RDN_ATTR=>${username},<PARENT_DN>. For example if you use parentDn of "ou=people,dc=admc,dc=com" and rdnAttribute of uid, then you would set


    "uid=${username},ou=people,dc=admc,dc=com"

By default the user name will be passed exactly as it is, so don't use this setter if that is what you want. (This works great for OpenLDAP with DIGEST-MD5 SASL, for example).

Params:
  • principalTemplate – template
/** * A template String containing place-holder token '${username}'. * All occurrences of '${username}' (without the quotes) will be translated * to the username that authentication is being attempted with. * <P> * If you supply a principalTemplate that does not contain '${username}', * then authentication will be user-independent. * <P> * It is common to authenticate to LDAP servers with the DN of the user's * LDAP entry. In this situation, set principalTemplate to * <CODE>&lt;RDN_ATTR=&gt;${username},&lt;PARENT_DN&gt;</CODE>. * For example if you use parentDn of * <CODE>"ou=people,dc=admc,dc=com"</CODE> and rdnAttribute of * <CODE>uid</CODE>, then you would set <PRE><CODE> * "uid=${username},ou=people,dc=admc,dc=com" * </CODE></PRE> * <P> * By default the user name will be passed exactly as it is, so don't use * this setter if that is what you want. (This works great for OpenLDAP * with DIGEST-MD5 SASL, for example). * * @param principalTemplate template */
public void setPrincipalTemplate(String principalTemplate) { this.principalTemplate = principalTemplate; }
Most users should not call this, and will get the default of "com.sun.jndi.ldap.LdapCtxFactory". Use this method if you prefer to use a context factory provided by your framework or container, for example, or if you are using a non-Sun JRE.
Params:
  • initialContextFactory – factory
/** * Most users should not call this, and will get the default of * "com.sun.jndi.ldap.LdapCtxFactory". * Use this method if you prefer to use a context factory provided by your * framework or container, for example, or if you are using a non-Sun JRE. * * @param initialContextFactory factory */
public void setInitialContextFactory(String initialContextFactory) { this.initialContextFactory = initialContextFactory; }
Some LDAP servers using a SASL mechanism require a realm to be specified, and some mechanisms allow a realm to be specified if you wish to use that feature. By default no realm will be sent to the LDAP server.

Don't use this setter if you are not setting a SASL mechanism.

Params:
  • saslRealm – realm
/** * Some LDAP servers using a SASL mechanism require a realm to be specified, * and some mechanisms allow a realm to be specified if you wish to use that * feature. * By default no realm will be sent to the LDAP server. * <P> * Don't use this setter if you are not setting a SASL mechanism. * </P> * * @param saslRealm realm */
public void setSaslRealm(String saslRealm) { this.saslRealm = saslRealm; }
Set DN which is parent of the user DNs. E.g. "ou=people,dc=admc,dc=com"
Params:
  • parentDn – parent DN
/** * Set DN which is parent of the user DNs. * E.g. "ou=people,dc=admc,dc=com" * * @param parentDn parent DN */
public void setParentDn(String parentDn) { this.parentDn = parentDn; }
rdnAttribute must hold the user name exactly as the HyperSQL login will be made with.

This is the RDN relative to the Parent DN specified with setParentDN. Defaults to 'uid'.

Params:
  • rdnAttribute – RDN attribute
See Also:
  • setParentDn(String)
/** * rdnAttribute must hold the user name exactly as the HyperSQL login will * be made with. * <P> * This is the RDN relative to the Parent DN specified with setParentDN. * Defaults to 'uid'. * </P> * * @see #setParentDn(String) * * @param rdnAttribute RDN attribute */
public void setRdnAttribute(String rdnAttribute) { this.rdnAttribute = rdnAttribute; }
Set the attribute name of the RDN + parentDn entries in which is stored the list of roles and optional schema for the authenticating user.

There is no default. You must set this attribute if you want LDAP instead of the local HyperSQL database to determine the user's roles! You must set the rolesSchemaAttribute property and/or the accessAttribute property. Consequently, if you do no tset this property, then you must set the accessAttribute property, and this LdapAuthBean will only determine access not roles.

To use the nice reverse group membership feature of LDAP, set this value to "memberof".

If you have set both rolesSchemaAttribute and this value, then the attribute set here will only be consulted if the accessAttribute check succeeds.

Params:
  • attribute – attribute
/** * Set the attribute name of the RDN + parentDn entries in which is stored * the list of roles and optional schema for the authenticating user. * <P> * There is no default. <b>You must set this attribute if you want LDAP * instead of the local HyperSQL database to determine the user's roles!</b> * You must set the rolesSchemaAttribute property and/or the * accessAttribute property. * Consequently, if you do no tset this property, then you must set the * accessAttribute property, and this LdapAuthBean will only determine * access not roles. * </P> <P> * To use the nice <i>reverse group membership</i> feature of LDAP, set * this value to "memberof". * </P> <P> * If you have set both rolesSchemaAttribute and this value, then the * attribute set here will only be consulted if the accessAttribute check * succeeds. * </P> * * @param attribute attribute */
public void setRolesSchemaAttribute(String attribute) { rolesSchemaAttribute = attribute; }
Set the attribute name of the RDN + parentDn entries which will be consulted to decide whether the user can access the HyperSQL database.

There is no default. If you set this attribute, then the attribute will determine whether the user can access the HyperSQL database, regardless of whether the rolesSchemaAttribute attribute is set.

If you set just this property, then the local HyperSQL database will decide all roles for the user. If you set this property and property rolesSchemaAttribute then this attribute will determine access, and if this attribute grants access then the rolesSchemaAttribute value will determine the user's roles.

Params:
  • attribute – attribute
/** * Set the attribute name of the RDN + parentDn entries which will be * consulted to decide whether the user can access the HyperSQL database. * <P> * There is no default. If you set this attribute, then the attribute will * determine whether the user can access the HyperSQL database, regardless * of whether the rolesSchemaAttribute attribute is set. * </P> <P> * If you set just this property, then the local HyperSQL database will * decide all roles for the user. If you set this property and property * rolesSchemaAttribute then this attribute will determine access, and if * this attribute grants access then the rolesSchemaAttribute value will * determine the user's roles. * </P> * * @param attribute attribute */
public void setAccessAttribute(String attribute) { accessAttribute = attribute; }
Params:
  • userName – String
  • password – String
Throws:
See Also:
  • authenticate.authenticate(String, String)
Returns:String[]
/** * * @see AuthFunctionBean#authenticate(String, String) * @param userName String * @param password String * @throws DenyException on access denial * @return String[] */
public String[] authenticate(String userName, String password) throws DenyException { if (!initialized) { throw new IllegalStateException( "You must invoke the 'init' method to initialize the " + LdapAuthBean.class.getName() + " instance."); } Hashtable<String, String> env = new Hashtable<String, String>(5, 0.75f); env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory); env.put(Context.PROVIDER_URL, "ldap://" + ldapHost + ((ldapPort == null) ? "" : (":" + ldapPort))); StartTlsResponse tlsResponse = null; LdapContext ctx = null; try { ctx = new InitialLdapContext(env, null); if (tls) { // Requesting to start TLS on an LDAP association tlsResponse = (StartTlsResponse) ctx.extendedOperation( new StartTlsRequest()); // Starting TLS tlsResponse.negotiate(); } // A TLS/SSL secure channel has been established if you reach here. // Assertion of client's authorization Identity -- Explicit way ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, mechanism); ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, ((principalTemplate == null) ? userName : principalTemplate.replace("${username}", userName))); ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password); if (saslRealm != null) { env.put("java.naming.security.sasl.realm", saslRealm); } // The Context.SECURITY_* authorizations are only applied when the // following statement executes. (Or any other remote operations done // while the TLS connection is still open). NamingEnumeration<SearchResult> sRess = null; try { sRess = ctx.search(parentDn, new BasicAttributes(rdnAttribute, userName), attributeUnion); } catch (AuthenticationException ae) { throw new DenyException(); } catch (Exception e) { throw new RuntimeException(e); } if (!sRess.hasMore()) { throw new DenyException(); } SearchResult sRes = sRess.next(); if (sRess.hasMore()) { throw new RuntimeException("> 1 result"); } Attributes attrs = sRes.getAttributes(); if (accessAttribute != null) { Attribute attribute = attrs.get(accessAttribute); if (attribute == null) { throw new DenyException(); } if (attribute.size() != 1) { throw new RuntimeException("Access attribute '" + accessAttribute + "' has unexpected value count: " + attribute.size()); } if (accessValuePattern != null) { Object accessValue = attribute.get(0); if (accessValue == null) { throw new RuntimeException( "Access Attr. value is null"); } if (!(accessValue instanceof String)) { throw new RuntimeException("Access Attr. value " + "not a String: " + accessValue.getClass().getName()); } if (!accessValuePattern.matcher( (String) accessValue).matches()) { throw new DenyException(); } } } if (rolesSchemaAttribute == null) { return null; } // If we reach here, then we definitely need to try to return a // list of roles + schema. List<String> returns = new ArrayList<String>(); Attribute attribute = attrs.get(rolesSchemaAttribute); if (attribute != null) { int valCount = attribute.size(); Matcher matcher; Object oneVal; for (int i = 0; i < valCount; i++) { oneVal = attribute.get(i); if (oneVal == null) { throw new RuntimeException( "R/S Attr value #" + i + " is null"); } if (!(oneVal instanceof String)) { throw new RuntimeException( "R/S Attr value #" + i + " not a String: " + oneVal.getClass().getName()); } if (roleSchemaValuePattern == null) { returns.add((String) oneVal); } else { matcher = roleSchemaValuePattern.matcher( (String) oneVal); if (matcher.matches()) { returns.add((matcher.groupCount() > 0) ? matcher.group(1) : (String) oneVal); } } } } if (returns.size() < 1) { if (accessAttribute == null) { throw new DenyException(); } return new String[0]; } return returns.toArray(new String[0]); } catch (DenyException de) { // This throws a non-runtime Exception, which is handled as an // access denial instead of a system problem. throw de; } catch (RuntimeException re) { throw re; } catch (IOException ioe) { throw new RuntimeException(ioe); } catch (NamingException ne) { throw new RuntimeException(ne); } finally { if (tlsResponse != null) try { tlsResponse.close(); } catch (IOException ioe) { logger.error("Failed to close TLS Response", ioe); } if (ctx != null) try { ctx.close(); } catch (NamingException ne) { logger.error("Failed to close LDAP Context", ne); } } } }