/* * JOSSO: Java Open Single Sign-On * * Copyright 2004-2009, Atricore, Inc. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.josso.gateway.identity.service.store.ldap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.josso.auth.Credential; import org.josso.auth.CredentialKey; import org.josso.auth.CredentialProvider; import org.josso.auth.BaseCredential; import org.josso.auth.scheme.AuthenticationScheme; import org.josso.auth.scheme.PasswordCredential; import org.josso.auth.scheme.UsernameCredential; import org.josso.gateway.SSOContext; import org.josso.gateway.SSONameValuePair; import org.josso.gateway.identity.exceptions.NoSuchUserException; import org.josso.gateway.identity.exceptions.SSOIdentityException; import org.josso.gateway.identity.service.BaseRole; import org.josso.gateway.identity.service.BaseRoleImpl; import org.josso.gateway.identity.service.BaseUser; import org.josso.gateway.identity.service.BaseUserImpl; import org.josso.gateway.identity.service.store.AbstractStore; import org.josso.gateway.identity.service.store.CertificateUserKey; import org.josso.gateway.identity.service.store.SimpleUserKey; import org.josso.gateway.identity.service.store.UserKey; import org.josso.gateway.identity.service.store.ExtendedIdentityStore; import org.josso.gateway.session.SSOSession; import org.josso.selfservices.ChallengeResponseCredential; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.*; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.StartTlsRequest; import javax.naming.ldap.StartTlsResponse; import java.io.IOException; import java.lang.reflect.Array; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.*; /** * An implementation of an Identity and Credential Store which obtains credential, user and * role information from an LDAP server using JNDI, based on the configuration properties. * <p/> * It allows to set whatever options your LDAP JNDI provider supports your Gateway * configuration file. * Examples of standard property names are: * <ul> * <li><code>initialContextFactory = "java.naming.factory.initial"</code> * <li><code>securityProtocol = "java.naming.security.protocol"</code> * <li><code>providerUrl = "java.naming.provider.url"</code> * <li><code>securityAuthentication = "java.naming.security.authentication"</code> * </ul> * <p/> * This store implementation is both an Identity Store and Credential Store. * Since in JOSSO the authentication of the user is left to the configured Authentication Scheme, * this store implementation cannot delegate user identity assertion by binding to the * LDAP server. For that reason it retrieves the required credentials from the directory * leaving the authentication procedure to the configured Authentication Scheme. * The store must be supplied with the configuratoin parameters so that it can retrieve user * identity information. * <p/> * <p/> * Additional component properties include: * <ul> * <li>securityPrincipal: the DN of the user to be used to bind to the LDAP Server * <li>securityCredential: the securityPrincipal password to be used for binding to the * LDAP Server. * <li>securityAuthentication: the security level to be used with the LDAP Server session. * Its value is one of the following strings: * "none", "simple", "strong". * If not set, "simple" will be used. * <li>ldapSearchScope : alows control over LDAP search scope : valid values are ONELEVEL, SUBTREE</li> * <li>usersCtxDN : the fixed distinguished name to the context to search for user accounts. * <li>principalUidAttributeID: the name of the attribute that contains the user login name. * This is used to locate the user. * <li>rolesCtxDN : The fixed distinguished name to the context to search for user roles. * <li>uidAttributeID: the name of the attribute that, in the object containing the user roles, * references role members. The attribute value should be the DN of the user associated with the * role. This is used to locate the user roles. * <li>roleAttributeID : The name of the attribute that contains the role name * <li>roleMatchingMOde : The way JOSSO gets users roles, values UDN (default) and UID. * <li>credentialQueryString : The query string to obtain user credentials. It should have the * following format : user_attribute_name=credential_attribute_name,... * For example : * uid=username,userPassword=password * <li>userPropertiesQueryString : The query string to obtain user properties. It should have * the following format : ldap_attribute_name=user_attribute_name,... * For example : * mail=mail,cn=description * </ul> * A sample LDAP Identity Store configuration : * <p/> * <pre> * <sso-identity-store> * <class>org.josso.gateway.identity.service.store.ldap.LDAPIdentityStore</class> * <initialContextFactory>com.sun.jndi.ldap.LdapCtxFactory</initialContextFactory> * <providerUrl>ldap://localhost</providerUrl> * <securityPrincipal>cn=Manager\,dc=my-domain\,dc=com</securityPrincipal> * <securityCredential>secret</securityCredential> * <securityAuthentication>simple</securityAuthentication> * <usersCtxDN>ou=People\,dc=my-domain\,dc=com</usersCtxDN> * <principalUidAttributeID>uid</principalUidAttributeID> * <rolesCtxDN>ou=Roles\,dc=my-domain\,dc=com</rolesCtxDN> * <uidAttributeID>uniquemember</uidAttributeID> * <roleMatchingMode>UDN</roleMatchingMode> * <roleAttributeID>cn</roleAttributeID> * <credentialQueryString>uid=username\,userPassword=password</credentialQueryString> * <userPropertiesQueryString>mail=mail\,cn=description</userPropertiesQueryString> * <ldapSearchScope>SUBTREE</ldapSearchScope> * </sso-identity-store> * </pre> * <p/> * A sample LDAP Credential Store configuration : * <p/> * <pre> * <credential-store> * <class>org.josso.gateway.identity.service.store.ldap.LDAPIdentityStore</class> * <initialContextFactory>com.sun.jndi.ldap.LdapCtxFactory</initialContextFactory> * <providerUrl>ldap://localhost</providerUrl> * <securityPrincipal>cn=Manager\,dc=my-domain\,dc=com</securityPrincipal> * <securityCredential>secret</securityCredential> * <securityAuthentication>simple</securityAuthentication> * <usersCtxDN>ou=People\,dc=my-domain\,dc=com</usersCtxDN> * <principalUidAttributeID>uid</principalUidAttributeID> * <rolesCtxDN>ou=Roles\,dc=my-domain\,dc=com</rolesCtxDN> * <uidAttributeID>uniquemember</uidAttributeID> * <roleAttributeID>cn</roleAttributeID> * <credentialQueryString>uid=username\,userPassword=password</credentialQueryString> * <userPropertiesQueryString>mail=mail\,cn=description</userPropertiesQueryString> * </credential-store> * </pre> * * @author <a href="mailto:gbrigand@josso.org">Gianluca Brigandi</a> * @version CVS $Id: LDAPIdentityStore.java 552 2008-04-25 13:38:29Z ajadzinsky $ * * @org.apache.xbean.XBean element="ldap-store" */ public class LDAPIdentityStore extends AbstractStore implements ExtendedIdentityStore { private static final Log logger = LogFactory.getLog(LDAPIdentityStore.class); /** * Valid userPassword schemes according to RFC 2307 */ private static final String USERPASSWORD_SCHEME_MD5 = "{md5}"; private static final String USERPASSWORD_SCHEME_CRYPT = "{crypt}"; private static final String USERPASSWORD_SCHEME_SHA = "{sha}"; // ----------------------------------------------------- Instance Variables private String _initialContextFactory; private String _providerUrl; private String _securityAuthentication; private String _rolesCtxDN; private String _uidAttributeID; private String _roleAttributeID; private String _roleMatchingMode; private String _securityProtocol; private String _securityPrincipal; private String _securityCredential; private String _principalUidAttributeID; private String _principalLookupAttributeID; private String _userCertificateAtrributeID; private String _usersCtxDN; private String _credentialQueryString; private String _userPropertiesQueryString; private String _ldapSearchScope; private String _updateableCredentialAttribute; private Boolean _useBindCredentials; private Boolean _enableStartTls; private String _trustStore; private String _trustStorePassword; // ----------------------------------------------------- Constructors public LDAPIdentityStore() { _userCertificateAtrributeID = "userCertificate"; _useBindCredentials = false; _enableStartTls = false; } // ----------------------------------------------------- IdentityStore Methods /** * Loads user information and its user attributes from the LDAP server. * * @param key the userid value to fetch the user in the LDAP server. * @return the user instance with the provided userid * @throws NoSuchUserException if the user does not exist * @throws SSOIdentityException a fatal exception loading the requested user */ public BaseUser loadUser(UserKey key) throws NoSuchUserException, SSOIdentityException { try { if (!(key instanceof SimpleUserKey)) { throw new SSOIdentityException("Unsupported key type : " + key.getClass().getName()); } String uid = selectUser(((SimpleUserKey) key).getId()); if (uid == null) { throw new NoSuchUserException(key); } BaseUser bu = new BaseUserImpl(); bu.setName(uid); List userProperties = new ArrayList(); // Optionally find user properties. if (getUserPropertiesQueryString() != null) { HashMap userPropertiesResultSet = selectUserProperties(((SimpleUserKey) key).getId()); Iterator i = userPropertiesResultSet.keySet().iterator(); while (i.hasNext()) { String pName = (String) i.next(); String pValue = (String) userPropertiesResultSet.get(pName); SSONameValuePair vp = new SSONameValuePair(pName, pValue); userProperties.add(vp); } } // Store User DN as a SSOUser property. String dn = selectUserDN(((SimpleUserKey) key).getId()); userProperties.add(new SSONameValuePair("josso.user.dn", dn)); SSONameValuePair[] props = (SSONameValuePair[]) userProperties.toArray(new SSONameValuePair[userProperties.size()]); bu.setProperties(props); return bu; } catch (NamingException e) { logger.error("NamingException while obtaining user", e); throw new SSOIdentityException("Error obtaining user : " + key); } catch (IOException e) { logger.error("StartTLS error", e); throw new SSOIdentityException("StartTLS error : " + e.getMessage()); } } /** * Retrieves the roles for the supplied user. * * @param key the user id of the user for whom role information is to be retrieved. * @return the roles associated with the supplied user. * @throws SSOIdentityException fatal exception obtaining user roles. */ public BaseRole[] findRolesByUserKey(UserKey key) throws SSOIdentityException { try { if (!(key instanceof SimpleUserKey)) { throw new SSOIdentityException("Unsupported key type : " + key.getClass().getName()); } String[] roleNames = selectRolesByUsername(((SimpleUserKey) key).getId()); List roles = new ArrayList(); for (int i = 0; i < roleNames.length; i++) { String roleName = roleNames[i]; BaseRole role = new BaseRoleImpl(); role.setName(roleName); roles.add(role); } return (BaseRole[]) roles.toArray(new BaseRole[roles.size()]); } catch (NamingException e) { logger.error("NamingException while obtaining roles", e); throw new SSOIdentityException("Error obtaining roles for user : " + key); } catch (IOException e) { logger.error("StartTLS error", e); throw new SSOIdentityException("StartTLS error : " + e.getMessage()); } } // ----------------------------------------------------- Extended IdentityStore Methods public String loadUsernameByRelayCredential ( ChallengeResponseCredential cred ) throws SSOIdentityException { try { return this.selectUser( cred.getId(), cred.getResponse() ); } catch(NamingException e) { logger.error("NamingException while obtaining user with relay credential", e); throw new SSOIdentityException("Error obtaining user with relay credential: ID[" + cred.getId() + "] = RESPONSE[" + cred.getResponse() + "]"); } catch (IOException e) { logger.error("StartTLS error", e); throw new SSOIdentityException("StartTLS error : " + e.getMessage()); } } public void updateAccountPassword ( UserKey key, Credential newPassword ) throws SSOIdentityException { try { if (!(key instanceof SimpleUserKey)) { throw new SSOIdentityException("Unsupported key type : " + key.getClass().getName()); } Attributes atts = new BasicAttributes( ); atts.put( this.getUpdateableCredentialAttribute(), ((BaseCredential)newPassword).getValue() ); this.replaceAttributes( this.selectUserDN( ((SimpleUserKey)key).getId() ), atts ); } catch (NamingException e) { logger.error("NamingException while updating password account", e); throw new SSOIdentityException("Error updating password account for user : " + key); } catch (IOException e) { logger.error("StartTLS error", e); throw new SSOIdentityException("StartTLS error : " + e.getMessage()); } } // ----------------------------------------------------- CredentialStore Methods /** * Loads user credential information for the supplied user from the LDAP server. * * @param key the user id of the user for whom credential information is to be retrieved. * @return the credentials associated with the supplied user. * @throws SSOIdentityException fatal exception obtaining user credentials */ public Credential[] loadCredentials(CredentialKey key, CredentialProvider cp) throws SSOIdentityException { try { if (!(key instanceof CredentialKey)) { throw new SSOIdentityException("Unsupported key type : " + key.getClass().getName()); } List credentials = new ArrayList(); HashMap credentialResultSet = selectCredentials(((SimpleUserKey) key).getId(), cp); Iterator i = credentialResultSet.keySet().iterator(); while (i.hasNext()) { String cName = (String) i.next(); List cValues = (List) credentialResultSet.get(cName); Iterator valIter = cValues.iterator(); while (valIter.hasNext()) { Credential c = cp.newCredential(cName, valIter.next()); credentials.add(c); } } return (Credential[]) credentials.toArray(new Credential[credentialResultSet.size()]); } catch (NamingException e) { logger.error("NamingException while obtaining Credentials", e); throw new SSOIdentityException("Error obtaining credentials for user : " + key); } catch (IOException e) { logger.error("StartTLS error", e); throw new SSOIdentityException("StartTLS error : " + e.getMessage()); } } /** * Loads user UID for the given credential key. * * @param key the key used to load UID from store. * @param cp credential provider * @throws SSOIdentityException */ public String loadUID(CredentialKey key, CredentialProvider cp) throws SSOIdentityException { try { if (key instanceof CertificateUserKey) { return loadUID(((CertificateUserKey)key).getId(), ((CertificateUserKey)key).getCertificate(), cp); } else if (key instanceof SimpleUserKey) { return ((SimpleUserKey)key).getId(); } else { throw new SSOIdentityException("Unsupported key type : " + key.getClass().getName()); } } catch (NamingException e) { logger.error("Failed to locate user", e); throw new SSOIdentityException("Failed to locate user for certificate : " + ((CertificateUserKey)key).getCertificate().getSubjectX500Principal().getName()); } catch (IOException e) { logger.error("StartTLS error", e); throw new SSOIdentityException("StartTLS error : " + e.getMessage()); } } @Override public boolean userExists(UserKey key) throws SSOIdentityException { if (getUseBindCredentials()) { String uid = null; try { uid = selectUser(((SimpleUserKey) key).getId()); } catch (NamingException e) { logger.error("NamingException while obtaining user", e); throw new SSOIdentityException("Error obtaining user : " + key); } catch (IOException e) { logger.error("StartTLS error", e); throw new SSOIdentityException("StartTLS error : " + e.getMessage()); } if (uid != null) { return true; } else { return false; } } return super.userExists(key); } // ----------------------------------------------------- LDAP Primitives /** * Obtains the roles for the given user. * * @param username the user name to fetch user data. * @return the list of roles to which the user is associated to. * @throws NamingException LDAP error obtaining roles fro the given user * @throws IOException */ protected String[] selectRolesByUsername(String username) throws NamingException, IOException { List userRoles = new ArrayList(); InitialLdapContext ctx = null; try { ctx = createLdapInitialContext(getUseBindCredentials()); } catch (NamingException e) { if (getUseBindCredentials()) { // in case we are using virtual identity store return (String[]) userRoles.toArray(new String[userRoles.size()]); } else { throw e; } } StartTlsResponse tls = null; if (getEnableStartTls()) { tls = startTls(ctx); } String rolesCtxDN = getRolesCtxDN(); // Search for any roles associated with the user if (rolesCtxDN != null) { // The attribute where user DN is stored in roles : String uidAttributeID = getUidAttributeID(); if (uidAttributeID == null) uidAttributeID = "uniquemember"; // The attribute that identifies the role name String roleAttrName = getRoleAttributeID(); if (roleAttrName == null) roleAttrName = "roles"; String userDN; if ("UID".equals(getRoleMatchingMode())) { // Use User ID to match the role userDN = username; } else { // Default behaviour: Match the role using the User DN, not just the username : userDN = selectUserDN(username); } if (userDN != null) { if (logger.isDebugEnabled()) logger.debug("Searching Roles for user '" + userDN + "' in Uid attribute name '" + uidAttributeID + "'"); try { if (userDN.contains("\\")) { logger.debug("Escaping '\\' character"); userDN = userDN.replace("\\", "\\\\\\"); } NamingEnumeration answer = ctx.search(rolesCtxDN, "(&(" + uidAttributeID + "=" + userDN + "))", getSearchControls()); if (logger.isDebugEnabled()) logger.debug("Search Name: " + rolesCtxDN); if (logger.isDebugEnabled()) logger.debug("Search Filter: (&(" + uidAttributeID + "=" + userDN + "))"); if (!answer.hasMore()) logger.info("No role where found for user " + username); while (answer.hasMore()) { SearchResult sr = (SearchResult) answer.next(); Attributes attrs = sr.getAttributes(); Attribute roles = attrs.get(roleAttrName); for (int r = 0; r < roles.size(); r++) { Object value = roles.get(r); String roleName = null; // The role attribute value is the role name roleName = value.toString(); if (roleName != null) { if (logger.isDebugEnabled()) logger.debug("Saving role '" + roleName + "' for user '" + username + "'"); userRoles.add(roleName); } } } } catch (NamingException e) { if (logger.isDebugEnabled()) logger.debug("Failed to locate roles", e); } } } // Close the context to release the connection if (tls != null) { tls.close(); } ctx.close(); return (String[]) userRoles.toArray(new String[userRoles.size()]); } /** * Fetches the supplied user DN. * * @param uid the user id * @return the user DN for the supplied uid * @throws NamingException LDAP error obtaining user information. * @throws IOException */ protected String selectUserDN(String uid) throws NamingException, IOException { String dn = null; InitialLdapContext ctx = createLdapInitialContext(false); StartTlsResponse tls = null; if (getEnableStartTls()) { tls = startTls(ctx); } String principalUidAttrName = this.getPrincipalUidAttributeID(); String usersCtxDN = this.getUsersCtxDN(); try { // NamingEnumeration answer = ctx.search(usersCtxDN, matchAttrs, principalAttr); // This gives more control over search behavior : NamingEnumeration answer = ctx.search(usersCtxDN, "(&(" + principalUidAttrName + "=" + uid + "))", getSearchControls()); while (answer.hasMore()) { SearchResult sr = (SearchResult) answer.next(); Attributes attrs = sr.getAttributes(); Attribute uidAttr = attrs.get(principalUidAttrName); if (uidAttr == null) { logger.warn("Invalid user uid attribute '" + principalUidAttrName + "'"); continue; } String uidValue = uidAttr.get().toString(); if (uidValue != null) { dn = sr.getName() + "," + usersCtxDN; if (logger.isDebugEnabled()) logger.debug("Found user '" + principalUidAttrName + "=" + uidValue + "' for user '" + uid + "' DN=" + dn); } else { if (logger.isDebugEnabled()) logger.debug("User not found for user '" + uid + "'"); } } } catch (NamingException e) { if (logger.isDebugEnabled()) logger.debug("Failed to locate user", e); } finally { // Close the context to release the connection if (tls != null) { tls.close(); } ctx.close(); } return dn; } protected String selectUser(String uid) throws NamingException, IOException { return selectUser( this.getPrincipalUidAttributeID(), uid ); } /** * Fetches the supplied user. * * @param attrValue the user id * @return the user id for the supplied uid * @throws NamingException LDAP error obtaining user information. * @throws IOException */ protected String selectUser(String attrId, String attrValue ) throws NamingException, IOException { String uidValue = null; InitialLdapContext ctx = createLdapInitialContext(false); StartTlsResponse tls = null; if (getEnableStartTls()) { tls = startTls(ctx); } BasicAttributes matchAttrs = new BasicAttributes(true); String uidAttrName = this.getPrincipalUidAttributeID(); String usersCtxDN = this.getUsersCtxDN(); matchAttrs.put( attrId, attrValue ); // String[] principalAttr = {attrId}; try { // NamingEnumeration answer = ctx.search(usersCtxDN, matchAttrs, principalAttr); // This gives more control over search behavior : NamingEnumeration answer = ctx.search(usersCtxDN, "(&(" + attrId + "=" + attrValue + "))", getSearchControls()); while (answer.hasMore()) { SearchResult sr = (SearchResult) answer.next(); Attributes attrs = sr.getAttributes(); Attribute uidAttr = attrs.get( uidAttrName ); if (uidAttr == null) { logger.warn("Invalid user attrValue attribute '" + uidAttrName + "'"); continue; } uidValue = uidAttr.get().toString(); if (uidValue != null) { if (logger.isDebugEnabled()) logger.debug("Found user '" + uidAttrName + "=" + uidValue + "' for user '" + attrValue + "'"); } else { if (logger.isDebugEnabled()) logger.debug("User not found for user '" + attrValue + "'"); } } } catch (NamingException e) { if (logger.isDebugEnabled()) logger.debug("Failed to locate user", e); } finally { // Close the context to release the connection if (tls != null) { tls.close(); } ctx.close(); } return uidValue; } /** * Fetch the Ldap user attributes to be used as credentials. * * @param uid the user id (or lookup value) for whom credentials are required * @return the hash map containing user credentials as name/value pairs * @throws NamingException LDAP error obtaining user credentials. * @throws IOException */ protected HashMap selectCredentials(String uid, CredentialProvider cp) throws NamingException, IOException { HashMap credentialResultSet = new HashMap(); InitialLdapContext ctx = createLdapInitialContext(false); StartTlsResponse tls = null; if (getEnableStartTls()) { tls = startTls(ctx); } String schemeName = null; if (cp instanceof AuthenticationScheme) { schemeName = ((AuthenticationScheme) cp).getName(); } String principalLookupAttrName = this.getPrincipalLookupAttributeID(); if (principalLookupAttrName == null || principalLookupAttrName.trim().equals("") || !"strong-authentication".equals(schemeName)) { principalLookupAttrName = this.getPrincipalUidAttributeID(); } String usersCtxDN = this.getUsersCtxDN(); // BasicAttributes matchAttrs = new BasicAttributes(true); // matchAttrs.put(principalUidAttrName, uid); String credentialQueryString = getCredentialQueryString(); HashMap credentialQueryMap = parseQueryString(credentialQueryString); Iterator i = credentialQueryMap.keySet().iterator(); List credentialAttrList = new ArrayList(); while (i.hasNext()) { String o = (String) i.next(); credentialAttrList.add(o); } String[] credentialAttr = (String[]) credentialAttrList.toArray(new String[credentialAttrList.size()]); try { // NamingEnumeration answer = ctx.search(usersCtxDN, matchAttrs, credentialAttr); // This gives more control over search behavior : NamingEnumeration answer = ctx.search(usersCtxDN, "(&(" + principalLookupAttrName + "=" + uid + "))", getSearchControls()); while (answer.hasMore()) { SearchResult sr = (SearchResult) answer.next(); Attributes attrs = sr.getAttributes(); String userDN = sr.getNameInNamespace(); if (logger.isDebugEnabled()) logger.debug("Processing results for entry '" + userDN + "'"); for (int j = 0; j < credentialAttr.length; j++) { if (attrs.get(credentialAttr[j]) == null) continue; //Object credentialObject = attrs.get(credentialAttr[j]).get(); String credentialName = (String) credentialQueryMap.get(credentialAttr[j]); String credentialValue = null; Attribute attr = attrs.get(credentialAttr[j]); NamingEnumeration attrEnum = attr.getAll(); while (attrEnum.hasMore()) { Object credentialObject = attrEnum.next(); if (credentialObject == null) continue; if (logger.isDebugEnabled()) logger.debug("Found user credential '" + credentialName + "' of type '" + credentialObject.getClass().getName() + "" + (credentialObject.getClass().isArray() ? "[" + Array.getLength(credentialObject) + "]" : "") + "'"); // if the attribute value is an array, cast it to byte[] and then convert to // String using proper encoding if (credentialObject.getClass().isArray()) { try { // Try to create a UTF-8 String, we use java.nio to handle errors in a better way. // If the byte[] cannot be converted to UTF-8, we're using the credentialObject as is. byte[] credentialData = (byte[]) credentialObject; ByteBuffer in = ByteBuffer.allocate(credentialData.length); in.put(credentialData); in.flip(); Charset charset = Charset.forName("UTF-8"); CharsetDecoder decoder = charset.newDecoder(); CharBuffer charBuffer = decoder.decode(in); credentialValue = charBuffer.toString(); } catch (CharacterCodingException e) { if (logger.isDebugEnabled()) logger.debug("Can't convert credential value to String using UTF-8"); } } else if (credentialObject instanceof String) { // The credential value must be a String ... credentialValue = (String) credentialObject; } // Check what do we have ... List credentials = (List) credentialResultSet.get(credentialName); if (credentials == null) { credentials = new ArrayList(); } if (credentialValue != null) { // Remove any schema information from the credential value, like the {md5} prefix for passwords. credentialValue = getSchemeFreeValue(credentialValue); credentials.add(credentialValue); } else { // We have a binary credential, leave it as it is ... probably binary value. credentials.add(credentialObject); } credentialResultSet.put(credentialName, credentials); if (logger.isDebugEnabled()) logger.debug("Found user credential '" + credentialName + "' with value '" + (credentialValue != null ? credentialValue : credentialObject) + "'"); } } } } catch (NamingException e) { if (logger.isDebugEnabled()) logger.debug("Failed to locate user", e); } finally { // Close the context to release the connection if (tls != null) { tls.close(); } ctx.close(); } return credentialResultSet; } /** * Get user UID attribute for the given certificate. * * @param lookupValue value used for credentials lookup * @param certificate user certificate * @param cp credential provider * @return user UID * @throws NamingException LDAP error obtaining user UID. * @throws IOException */ protected String loadUID(String lookupValue, X509Certificate certificate, CredentialProvider cp) throws NamingException, IOException { String uidValue = null; InitialLdapContext ctx = createLdapInitialContext(false); StartTlsResponse tls = null; if (getEnableStartTls()) { tls = startTls(ctx); } String schemeName = null; if (cp instanceof AuthenticationScheme) { schemeName = ((AuthenticationScheme) cp).getName(); } String principalLookupAttrName = this.getPrincipalLookupAttributeID(); if (principalLookupAttrName == null || principalLookupAttrName.trim().equals("") || !"strong-authentication".equals(schemeName)) { principalLookupAttrName = this.getPrincipalUidAttributeID(); } String principalUidAttrName = this.getPrincipalUidAttributeID(); String certificateAttrName = this.getUserCertificateAtrributeID(); String usersCtxDN = this.getUsersCtxDN(); try { // NamingEnumeration answer = ctx.search(usersCtxDN, matchAttrs, principalAttr); // This gives more control over search behavior : NamingEnumeration answer = ctx.search(usersCtxDN, "(&(" + principalLookupAttrName + "={0})(" + certificateAttrName + "={1}))", new Object[] {lookupValue, certificate.getEncoded()}, getSearchControls()); while (answer.hasMore()) { SearchResult sr = (SearchResult) answer.next(); Attributes attrs = sr.getAttributes(); Attribute uidAttr = attrs.get(principalUidAttrName); if (uidAttr == null) { logger.warn("Invalid user uid attribute '" + principalUidAttrName + "'"); continue; } uidValue = uidAttr.get().toString(); if (uidValue != null) { if (logger.isDebugEnabled()) logger.debug("Found user " + principalUidAttrName + "=" + uidValue); } else { if (logger.isDebugEnabled()) logger.debug("User not found for certificate '" + certificate.getSubjectX500Principal().getName() + "'"); } } } catch (NamingException e) { if (logger.isDebugEnabled()) logger.debug("Failed to locate user", e); } catch (CertificateEncodingException e) { if (logger.isDebugEnabled()) logger.debug("Certificate encoding exception", e); } finally { // Close the context to release the connection if (tls != null) { tls.close(); } ctx.close(); } return uidValue; } /** * Obtain the properties for the user associated with the given uid using the * configured user properties query string. * * @param uid the user id of the user for whom its user properties are required. * @return the hash map containing user properties as name/value pairs. * @throws NamingException LDAP error obtaining user properties. * @throws IOException */ protected HashMap selectUserProperties(String uid) throws NamingException, IOException { HashMap userPropertiesResultSet = new HashMap(); InitialLdapContext ctx = null; try { ctx = createLdapInitialContext(getUseBindCredentials()); } catch (NamingException e) { if (getUseBindCredentials()) { // in case we are using virtual identity store return userPropertiesResultSet; } else { throw e; } } StartTlsResponse tls = null; if (getEnableStartTls()) { tls = startTls(ctx); } BasicAttributes matchAttrs = new BasicAttributes(true); String principalUidAttrName = this.getPrincipalUidAttributeID(); String usersCtxDN = this.getUsersCtxDN(); matchAttrs.put(principalUidAttrName, uid); String userPropertiesQueryString = getUserPropertiesQueryString(); HashMap userPropertiesQueryMap = parseQueryString(userPropertiesQueryString); Iterator i = userPropertiesQueryMap.keySet().iterator(); List propertiesAttrList = new ArrayList(); while (i.hasNext()) { String o = (String) i.next(); propertiesAttrList.add(o); } String[] propertiesAttr = (String[]) propertiesAttrList.toArray( new String[propertiesAttrList.size()] ); try { // This gives more control over search behavior : NamingEnumeration answer = ctx.search(usersCtxDN, "(&(" + principalUidAttrName + "=" + uid + "))", getSearchControls()); while (answer.hasMore()) { SearchResult sr = (SearchResult) answer.next(); Attributes attrs = sr.getAttributes(); for (int j = 0; j < propertiesAttr.length; j++) { Attribute attribute = attrs.get(propertiesAttr[j]); if (attribute == null) { logger.warn("Invalid user property attribute '" + propertiesAttr[j] + "'"); continue; } Object propertyObject = attrs.get(propertiesAttr[j]).get(); if (propertyObject == null) { logger.warn("Found a 'null' value for user property '" + propertiesAttr[j] + "'"); continue; } String propertyValue = propertyObject.toString(); String propertyName = (String) userPropertiesQueryMap.get(propertiesAttr[j]); userPropertiesResultSet.put(propertyName, propertyValue); if (logger.isDebugEnabled()) logger.debug("Found user property '" + propertyName + "' with value '" + propertyValue + "'"); } } } catch (NamingException e) { if (logger.isDebugEnabled()) logger.debug("Failed to locate user", e); } finally { // Close the context to release the connection if (tls != null) { tls.close(); } ctx.close(); } return userPropertiesResultSet; } protected void replaceAttributes(String bane, Attributes atts) throws NamingException, IOException { InitialLdapContext ctx = null; try { ctx = createLdapInitialContext(getUseBindCredentials()); } catch (NamingException e) { if (getUseBindCredentials()) { // in case we are using virtual identity store return; } else { throw e; } } ctx.modifyAttributes( bane, InitialLdapContext.REPLACE_ATTRIBUTE, atts ); } /** * Creates an InitialLdapContext by logging into the configured Ldap Server using the configured * username and credential. * * @return the Initial Ldap Context to be used to perform searches, etc. * @throws NamingException LDAP binding error. * @throws IOException */ protected InitialLdapContext createLdapInitialContext(Boolean useBindCredentials) throws NamingException, IOException { String securityPrincipal = getSecurityPrincipal(); if (securityPrincipal == null) securityPrincipal = ""; String securityCredential = getSecurityCredential(); if (securityCredential == null) securityCredential = ""; SSOSession session = SSOContext.getCurrent().getSession(); if (useBindCredentials && session != null) { //String username = session.getUsername(); String username = getUsername(session.getSubject().getPublicCredentials()); securityPrincipal = selectUserDN(username); if (securityPrincipal == null) { // in case of virtual identity store throw new NamingException("User not found."); } securityCredential = getPassword(session.getSubject().getPrivateCredentials()); } return createLdapInitialContext(securityPrincipal, securityCredential); } /** * Creates an InitialLdapContext by logging into the configured Ldap Server using the provided * username and credential. * * @return the Initial Ldap Context to be used to perform searches, etc. * @throws NamingException LDAP binding error. */ protected InitialLdapContext createLdapInitialContext(String securityPrincipal, String securityCredential) throws NamingException { Properties env = new Properties(); env.setProperty(Context.INITIAL_CONTEXT_FACTORY, getInitialContextFactory()); env.setProperty(Context.SECURITY_AUTHENTICATION, getSecurityAuthentication()); env.setProperty(Context.PROVIDER_URL, getProviderUrl()); env.setProperty(Context.SECURITY_PROTOCOL, (getSecurityProtocol() == null ? "" : getSecurityProtocol())); // Set defaults for key values if they are missing String factoryName = env.getProperty(Context.INITIAL_CONTEXT_FACTORY); if (factoryName == null) { factoryName = "com.sun.jndi.ldap.LdapCtxFactory"; env.setProperty(Context.INITIAL_CONTEXT_FACTORY, factoryName); } String authType = env.getProperty(Context.SECURITY_AUTHENTICATION); if (authType == null) env.setProperty(Context.SECURITY_AUTHENTICATION, "simple"); String protocol = env.getProperty(Context.SECURITY_PROTOCOL); String providerURL = getProviderUrl(); // Use localhost if providerUrl not set if (providerURL == null) { //providerURL = "ldap://localhost:" + ((protocol != null && protocol.equals("ssl")) ? "636" : "389"); if (protocol != null && protocol.equals("ssl")) { // We should use Start TLS extension? providerURL = "ldaps://localhost:636"; } else { providerURL = "ldap://localhost:389"; } } env.setProperty(Context.PROVIDER_URL, providerURL); env.setProperty(Context.SECURITY_PRINCIPAL, securityPrincipal); env.put(Context.SECURITY_CREDENTIALS, securityCredential); // always follow referrals transparently env.put(Context.REFERRAL, "follow"); // Logon into LDAP server if (logger.isDebugEnabled()) logger.debug("Logging into LDAP server, env=" + env); InitialLdapContext ctx = new InitialLdapContext(env, null); if (logger.isDebugEnabled()) logger.debug("Logged into LDAP server, " + ctx); return ctx; } protected StartTlsResponse startTls(InitialLdapContext ctx) throws NamingException, IOException { if (getTrustStore() != null && !getTrustStore().equals("")) { System.setProperty("javax.net.ssl.trustStore", getTrustStore()); } if (getTrustStorePassword() != null && !getTrustStorePassword().equals("")) { System.setProperty("javax.net.ssl.trustStorePassword", getTrustStorePassword()); } // Specify client's keyStore where client's certificate is located. // Note: Client's keyStore is optional for StartTLS negotiation and connection, // but it is required for implicit client indendity assertion // by SASL EXTERNAL where client ID is extracted from certificate subject. //System.setProperty("javax.net.ssl.keyStore", "myKey.pfx"); //System.setProperty("javax.net.ssl.keyStoreType", "pkcs12"); //System.setProperty("javax.net.ssl.keyStorePassword", "secret"); StartTlsResponse tls = (StartTlsResponse) ctx.extendedOperation(new StartTlsRequest()); tls.negotiate(); return tls; } /** * Gets the username from the received credentials. * * @param credentials */ protected String getUsername(Set credentials) { UsernameCredential c = getUsernameCredential(credentials); if (c == null) return null; return (String) c.getValue(); } /** * Gets the credential that represents a Username. */ protected UsernameCredential getUsernameCredential(Set credentials) { Iterator i = credentials.iterator(); while (i.hasNext()) { Credential credential = (Credential) i.next(); if (credential instanceof UsernameCredential) { return (UsernameCredential) credential; } } return null; } /** * Gets the password from the recevied credentials. * * @param credentials */ protected String getPassword(Set credentials) { PasswordCredential p = getPasswordCredential(credentials); if (p == null) return null; return (String) p.getValue(); } /** * Gets the credential that represents a password. * * @param credentials */ protected PasswordCredential getPasswordCredential(Set credentials) { Iterator i = credentials.iterator(); while (i.hasNext()) { Credential credential = (Credential) i.next(); if (credential instanceof PasswordCredential) { return (PasswordCredential) credential; } } return null; } /** * Returns the supplied attribute value without the scheme prefix. * This method should be invoked for 'userPassword' type attributes. * * @param attributeValue the attribute value * @return the scheme */ protected String getSchemeFreeValue(String attributeValue) { String targetValue = attributeValue; if (attributeValue.toLowerCase().startsWith(LDAPIdentityStore.USERPASSWORD_SCHEME_CRYPT)) { targetValue = attributeValue.substring(USERPASSWORD_SCHEME_CRYPT.length()); } else if (attributeValue.toLowerCase().startsWith(LDAPIdentityStore.USERPASSWORD_SCHEME_MD5)) { targetValue = attributeValue.substring(USERPASSWORD_SCHEME_MD5.length()); } else if (attributeValue.toLowerCase().startsWith(LDAPIdentityStore.USERPASSWORD_SCHEME_SHA)) { targetValue = attributeValue.substring(USERPASSWORD_SCHEME_SHA.length()); } return targetValue; } // ----------------------------------------------------- Utils /** * Parses a credential query string and builds a HashMap object * with key-value pairs. * The query string should be in the form of a string * and should have key-value pairs in the form <i>key=value</i>, * with each pair separated from the next by a ',' character. * * @param s a string containing the query to be parsed * @return a HashMap object built from the parsed key-value pairs * @throws IllegalArgumentException if the query string * is invalid */ protected HashMap parseQueryString(String s) { if (s == null) { throw new IllegalArgumentException(); } HashMap hm = new HashMap(); StringTokenizer st = new StringTokenizer(s, ","); while (st.hasMoreTokens()) { String pair = (String) st.nextToken(); int pos = pair.indexOf('='); if (pos == -1) { // XXX // should give more detail about the illegal argument throw new IllegalArgumentException(); } String key = pair.substring(0, pos); String val = pair.substring(pos + 1, pair.length()); hm.put(key, val); } return hm; } /** * This method returns the proper search controls to be used when querying the LDAP.. */ protected SearchControls getSearchControls() { SearchControls sc = new SearchControls(); sc.setSearchScope(_ldapSearchScope == null || _ldapSearchScope.equalsIgnoreCase("ONELEVEL") ? SearchControls.ONELEVEL_SCOPE : SearchControls.SUBTREE_SCOPE); return sc; } /** * Configuration Properties */ // ----------------------------------------------------- Configuration Properties public void setInitialContextFactory(String initialContextFactory) { _initialContextFactory = initialContextFactory; } public String getInitialContextFactory() { return _initialContextFactory; } public void setProviderUrl(String providerUrl) { _providerUrl = providerUrl; } public String getProviderUrl() { return _providerUrl; } public void setSecurityAuthentication(String securityAuthentication) { _securityAuthentication = securityAuthentication; } public String getSecurityAuthentication() { return _securityAuthentication; } public void setSecurityProtocol(String securityProtocol) { _securityProtocol = securityProtocol; } public String getSecurityProtocol() { return _securityProtocol; } public void setSecurityPrincipal(String securityPrincipal) { _securityPrincipal = securityPrincipal; } public String getSecurityPrincipal() { return _securityPrincipal; } public void setSecurityCredential(String securityCredential) { _securityCredential = securityCredential; } protected String getSecurityCredential() { return _securityCredential; } public String getLdapSearchScope() { return _ldapSearchScope; } public void setLdapSearchScope(String ldapSearchScope) { _ldapSearchScope = ldapSearchScope; } public void setUsersCtxDN(String usersCtxDN) { _usersCtxDN = usersCtxDN; } public String getUsersCtxDN() { return _usersCtxDN; } public void setRolesCtxDN(String rolesCtxDN) { _rolesCtxDN = rolesCtxDN; } public String getRolesCtxDN() { return _rolesCtxDN; } public void setPrincipalUidAttributeID(String principalUidAttributeID) { _principalUidAttributeID = principalUidAttributeID; } public String getPrincipalUidAttributeID() { return _principalUidAttributeID; } public void setUidAttributeID(String uidAttributeID) { _uidAttributeID = uidAttributeID; } public void setPrincipalLookupAttributeID(String principalLookupAttributeID) { _principalLookupAttributeID = principalLookupAttributeID; } public String getPrincipalLookupAttributeID() { return _principalLookupAttributeID; } public void setUserCertificateAtrributeID(String userCertificateAtrributeID) { _userCertificateAtrributeID = userCertificateAtrributeID; } public String getUserCertificateAtrributeID() { return _userCertificateAtrributeID; } public String getRoleMatchingMode() { return _roleMatchingMode; } public void setRoleMatchingMode(String roleMatchingMode) { this._roleMatchingMode = roleMatchingMode; } public String getUidAttributeID() { return _uidAttributeID; } public void setRoleAttributeID(String roleAttributeID) { _roleAttributeID = roleAttributeID; } public String getRoleAttributeID() { return _roleAttributeID; } public void setCredentialQueryString(String credentialQueryString) { _credentialQueryString = credentialQueryString; } public String getCredentialQueryString() { return _credentialQueryString; } public void setUserPropertiesQueryString(String userPropertiesQueryString) { _userPropertiesQueryString = userPropertiesQueryString; } public String getUserPropertiesQueryString() { return _userPropertiesQueryString; } public String getUpdateableCredentialAttribute () { return _updateableCredentialAttribute; } public void setUpdateableCredentialAttribute ( String updateableCredentialAttribute ) { this._updateableCredentialAttribute = updateableCredentialAttribute; } public Boolean getUseBindCredentials() { return _useBindCredentials; } public void setUseBindCredentials(Boolean useBindCredentials) { _useBindCredentials = useBindCredentials; } public Boolean getEnableStartTls() { return _enableStartTls; } public void setEnableStartTls(Boolean enableStartTls) { _enableStartTls = enableStartTls; } public String getTrustStore() { return _trustStore; } public void setTrustStore(String trustStore) { _trustStore = trustStore; } public String getTrustStorePassword() { return _trustStorePassword; } public void setTrustStorePassword(String trustStorePassword) { _trustStorePassword = trustStorePassword; } }