[jboss-cvs] Picketbox SVN: r280 - in trunk/security-jboss-sx/jbosssx/src: test/java/org/jboss/test/authentication/cbh and 1 other directory.

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Wed Nov 2 11:23:00 EDT 2011


Author: anil.saldhana at jboss.com
Date: 2011-11-02 11:23:00 -0400 (Wed, 02 Nov 2011)
New Revision: 280

Added:
   trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/DecodeAction.java
   trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/LdapCallbackHandler.java
   trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/VerifyPasswordCallback.java
   trunk/security-jboss-sx/jbosssx/src/test/java/org/jboss/test/authentication/cbh/LdapCallbackHandlerUnitTestCase.java
Modified:
   trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/DatabaseCallbackHandler.java
   trunk/security-jboss-sx/jbosssx/src/test/java/org/jboss/test/authentication/cbh/DatabaseCallbackHandlerUnitTestCase.java
Log:
SECURITY-467: SECURITY-470:  db and ldap callback handlers

Modified: trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/DatabaseCallbackHandler.java
===================================================================
--- trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/DatabaseCallbackHandler.java	2011-11-01 20:48:30 UTC (rev 279)
+++ trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/DatabaseCallbackHandler.java	2011-11-02 15:23:00 UTC (rev 280)
@@ -36,6 +36,7 @@
 import javax.security.auth.callback.CallbackHandler;
 import javax.security.auth.callback.PasswordCallback;
 import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.CredentialException;
 import javax.sql.DataSource;
 
 import org.jboss.logging.Logger;
@@ -255,13 +256,21 @@
 	 * Handle a {@code Callback}
 	 * @param c callback
 	 * @throws UnsupportedCallbackException If the callback is not supported by this handler
+	 * @throws IOException 
 	 */
-	protected void handleCallBack( Callback c ) throws UnsupportedCallbackException
+	protected void handleCallBack( Callback c ) throws UnsupportedCallbackException, IOException
 	{ 
 		if(c instanceof VerifyPasswordCallback)
 		{
 			VerifyPasswordCallback vpc = (VerifyPasswordCallback) c;
-			handleVerification(vpc);
+			try 
+			{
+				handleVerification(vpc);
+			} 
+			catch (CredentialException e) 
+			{
+				throw new IOException(e);
+			}
 		}
 		if(c instanceof PasswordCallback == false)
 			return;
@@ -271,7 +280,7 @@
 		passwdCallback.setPassword(getPassword().toCharArray());
 	}
 	
-	protected void handleVerification(VerifyPasswordCallback vpc)
+	protected void handleVerification(VerifyPasswordCallback vpc) throws CredentialException
 	{
 		String userPass = vpc.getValue();
 		String passwordFromDB = getPassword();
@@ -279,6 +288,10 @@
 		{
 			vpc.setVerified(true);
 		}
+		else
+		{
+			throw new CredentialException(ErrorCodes.ACCESS_DENIED + "Passwords don't match");
+		}
 	}
 	
 	private String getPassword()

Added: trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/DecodeAction.java
===================================================================
--- trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/DecodeAction.java	                        (rev 0)
+++ trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/DecodeAction.java	2011-11-02 15:23:00 UTC (rev 280)
@@ -0,0 +1,100 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2005, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* 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.jboss.security.auth.callback;
+
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.jboss.security.util.MBeanServerLocator;
+
+/**
+ * PriviledgedActions used by login modules for decoding passwords
+ * 
+ * @author Scott.Stark at jboss.org
+ * @version $Revision: 2 $
+ */
+class DecodeAction implements PrivilegedExceptionAction<Object>
+{
+   /** The permission required to access decode, decode64 */
+   private static final RuntimePermission decodePermission =
+      new RuntimePermission("org.jboss.security.auth.spi.DecodeAction.decode"); 
+   
+   String password;
+   ObjectName serviceName;
+
+   DecodeAction(String password, ObjectName serviceName)
+   {
+      this.password = password;
+      this.serviceName = serviceName;
+   }
+
+   /**
+    * 
+    * @return
+    * @throws Exception
+    */
+   public Object run() throws Exception
+   {  
+      // Invoke the decodeb64 op
+      byte[] secret = decode64(password);
+      // Convert to UTF-8 base char array
+      String secretPassword = new String(secret, "UTF-8");
+      return secretPassword.toCharArray();
+   }
+   
+   /** Decrypt the secret using the cipherKey.
+   *
+   * @param secret - the encrypted secret to decrypt.
+   * @return the decrypted secret
+   * @throws Exception
+   */
+  private byte[] decode64(String secret)
+     throws Exception
+  {
+     SecurityManager sm = System.getSecurityManager();
+     if( sm != null )
+        sm.checkPermission(decodePermission);
+
+     MBeanServer server = MBeanServerLocator.locateJBoss();
+     return (byte[]) server.invoke(serviceName, "decode64", new Object[] {secret}, 
+           new String[] {String.class.getName()});
+  }
+  
+   static char[] decode(String password, ObjectName serviceName)
+      throws Exception
+   {
+      DecodeAction action = new DecodeAction(password, serviceName);
+      try
+      {
+         char[] decode = (char[]) AccessController.doPrivileged(action);
+         return decode;
+      }
+      catch(PrivilegedActionException e)
+      {
+         throw e.getException();
+      }
+   }
+}


Property changes on: trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/DecodeAction.java
___________________________________________________________________
Added: svn:executable
   + *

Added: trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/LdapCallbackHandler.java
===================================================================
--- trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/LdapCallbackHandler.java	                        (rev 0)
+++ trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/LdapCallbackHandler.java	2011-11-02 15:23:00 UTC (rev 280)
@@ -0,0 +1,534 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2011, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * 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.jboss.security.auth.callback;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+
+import javax.management.ObjectName;
+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.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.InitialLdapContext;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.jboss.logging.Logger;
+import org.jboss.security.ErrorCodes;
+
+/**
+ * <p>
+ * A {@code CallbackHandler} using the LDAP to match the passed password.
+ * </p>
+ * <p>
+ * There are two callbacks that can be passed to this handler.
+ * <ol>
+ * <li>{@code PasswordCallback}:  Passing this callback will get the password for the user. 
+ *                                The returned password will not be in clear text. It will
+ *                                be in the hashed form the ldap server has stored.
+ * </li>
+ * <li>{@code VerifyPasswordCallback} Passing this callback with a value will make the handler
+ *                                to do a ldap bind to verify the user password.
+ * </li>
+ * </ol>
+ * </p>
+ * <p>
+ * The main method is {@code #setConfiguration(Map)} which takes in a map of String key/value pairs.
+ * The possible pairs are:
+ * <ol>
+ * <li>passwordAttributeID  :  what is the name of the attribute where the password is stored. Default: userPassword</li>
+ * <li>bindDN   :  DN used to bind against the ldap server with read/write permissions for baseCtxDN.</li>
+ * <li>bindCredential : Password for the bindDN. This can be encrypted if the jaasSecurityDomain is specified.</li>
+ * <li>baseCtxDN : The fixed DN of the context to start the user search from.</li>
+ * <li>baseFilter: A search filter used to locate the context of the user to authenticate. 
+ *                 The input username/userDN as provided by the {@code NameCallback} 
+ *                 will be substituted into the filter anywhere a "{0}" expression is seen. 
+ *                 This substitution behavior comes from the standard.</li>
+ * <li>searchTimeLimit : The timeout in milliseconds for the user/role searches. Defaults to 10000 (10 seconds).</li>
+ * <li>jaasSecurityDomain : The JMX ObjectName of the JaasSecurityDomain to use to decrypt the java.naming.security.principal. 
+ *                          The encrypted form of the password is that returned by the JaasSecurityDomain#encrypt64(byte[]) method. 
+ *                          The org.jboss.security.plugins.PBEUtils can also be used to generate the encrypted form.</li>
+ * <li>distinguishedNameAttribute : Used in ldap servers such as Active Directory where the ldap provider has a property (distinguishedName)
+ *                                  to return the relative CN of the user. Default: distinguishedName</li>
+ * </ol>
+ * </p>
+ * <p>
+ * Example Usages:
+ * <pre>
+ *  LdapCallbackHandler cbh = new LdapCallbackHandler();
+ *  Map<String,String> map = new HashMap<String,String>();
+ *  map.put("bindDN", "cn=Directory Manager");
+ *  map.put("bindCredential", "password");
+ *  map.put("baseFilter", "(uid={0})");
+ *  map.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
+ *  map.put("java.naming.provider.url", "ldap://localhost:10389");
+ *  map.put("baseCtxDN", "ou=People,dc=jboss,dc=org");
+ *  cbh.setConfiguration(map);
+ *  NameCallback ncb = new NameCallback("Enter");
+ *  ncb.setName("jduke");
+ *  VerifyPasswordCallback vpc = new VerifyPasswordCallback();
+ *  vpc.setValue("theduke");
+ *  cbh.handle(new Callback[] {ncb,vpc} );
+ *  assertTrue(vpc.isVerified());
+ * </pre>
+ * </p>
+ * @author Anil Saldhana
+ * @since Nov 1, 2011
+ */
+public class LdapCallbackHandler extends AbstractCallbackHandler implements CallbackHandler 
+{
+	protected static Logger log = Logger.getLogger(LdapCallbackHandler.class);
+	protected boolean trace = log.isTraceEnabled();
+
+	private static final String PASSWORD_ATTRIBUTE_ID = "passwordAttributeID";
+
+	private static final String BIND_DN = "bindDN";
+
+	private static final String BIND_CREDENTIAL = "bindCredential";
+
+	private static final String BASE_CTX_DN = "baseCtxDN";
+
+	private static final String BASE_FILTER_OPT = "baseFilter"; 
+
+	private static final String SEARCH_TIME_LIMIT_OPT = "searchTimeLimit"; 
+
+	private static final String SECURITY_DOMAIN_OPT = "jaasSecurityDomain";
+	   
+	private static final String DISTINGUISHED_NAME_ATTRIBUTE_OPT = "distinguishedNameAttribute";
+
+	protected String bindDN;
+
+	protected String bindCredential;
+
+	protected String baseDN;
+
+	protected String baseFilter; 
+
+	protected String passwordAttributeID = "userPassword";
+ 
+	protected int recursion = 0;
+
+	protected int searchTimeLimit = 10000;
+
+	protected int searchScope = SearchControls.SUBTREE_SCOPE; 
+
+	protected String distinguishedNameAttribute;
+
+	protected boolean parseUsername;
+
+	protected String usernameBeginString;
+
+	protected String usernameEndString;
+
+	// simple flag to indicate is the validatePassword method was called
+	protected boolean isPasswordValidated = false;
+	
+	protected Map<String,String> options = new HashMap<String, String>();
+	
+	public LdapCallbackHandler()
+	{	
+	}
+	
+	public void setConfiguration(Map<String,String> config)
+	{
+		if(config != null)
+		{
+			options.putAll(config);
+		}
+	}
+
+	public void handle(Callback[] callbacks) throws IOException,
+	UnsupportedCallbackException 
+	{
+		if(userName == null)
+		{
+			userName = getUserName(callbacks);
+		}
+		for (int i = 0; i < callbacks.length; i++)
+		{
+			Callback callback = callbacks[i];
+			try 
+			{
+				this.handleCallBack( callback );
+			} 
+			catch (NamingException e) 
+			{
+				throw new IOException(e);
+			} 
+		}
+	}
+	
+	/**
+	 * Handle a {@code Callback}
+	 * @param c callback
+	 * @throws UnsupportedCallbackException If the callback is not supported by this handler
+	 * @throws NamingException 
+	 */
+	protected void handleCallBack( Callback c ) throws UnsupportedCallbackException, NamingException
+	{ 
+		if(c instanceof VerifyPasswordCallback)
+		{
+			verifyPassword((VerifyPasswordCallback) c);
+			return;
+		}
+		
+		if(c instanceof PasswordCallback == false)
+			return;
+
+		PasswordCallback passwdCallback = (PasswordCallback) c;
+
+		String bindDN = getBindDN();
+		
+		String bindCredential = getBindCredential(); 
+		
+		String tmp = options.get(PASSWORD_ATTRIBUTE_ID);
+		if(tmp != null && tmp.length() > 0)
+		{
+			passwordAttributeID = tmp;
+		}
+
+		InitialLdapContext ctx;
+		ClassLoader currentTCCL = SecurityActions.getContextClassLoader();
+		try
+		{
+			if (currentTCCL != null)
+				SecurityActions.setContextClassLoader(null);
+			ctx = this.constructInitialLdapContext(bindDN, bindCredential);
+		}
+		catch (NamingException e)
+		{
+			throw new RuntimeException(e);
+		} 
+
+		String timeLimit = (String) options.get(SEARCH_TIME_LIMIT_OPT);
+		if (timeLimit != null)
+		{
+			try
+			{
+				searchTimeLimit = Integer.parseInt(timeLimit);
+			}
+			catch (NumberFormatException e)
+			{
+				if (trace)
+					log.trace("Failed to parse: " + timeLimit + ", using searchTimeLimit=" + searchTimeLimit, e);
+			}
+		}
+		if(searchTimeLimit == 0)
+			searchTimeLimit = 10000;
+
+		String baseDN = (String) options.get(BASE_CTX_DN); 
+		String baseFilter = (String) options.get(BASE_FILTER_OPT);
+
+		SearchControls constraints = new SearchControls();
+		constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
+
+		constraints.setTimeLimit(searchTimeLimit);
+
+
+		NamingEnumeration<SearchResult> results = null;
+
+		Object[] filterArgs = {userName};
+		try
+		{
+			if(baseDN == null)
+				throw new NamingException(ErrorCodes.NULL_VALUE + BASE_CTX_DN + " is null");
+			results = ctx.search(baseDN, baseFilter, filterArgs, constraints);
+			if (results.hasMore() == false)
+			{
+				safeClose(results);
+				throw new NamingException(ErrorCodes.PROCESSING_FAILED + "Search of baseDN(" + baseDN + ") found no matches");
+			} 
+			SearchResult sr = results.next();
+			String name = sr.getName();
+			String userDN = null;
+			if (sr.isRelative() == true)
+				userDN = name + "," + baseDN;
+			else
+				throw new NamingException(ErrorCodes.PROCESSING_FAILED + "Can't follow referal for authentication: " + name);
+
+			safeClose(results);
+
+			//Finished Authentication.  Lets look for the attributes
+			filterArgs = new Object[]{userName, userDN};
+			results = ctx.search(userDN, baseFilter, filterArgs, constraints);
+			try
+			{
+				while (results.hasMore())
+				{
+					sr = (SearchResult) results.next(); 
+					Attributes attributes = sr.getAttributes();
+					NamingEnumeration<? extends javax.naming.directory.Attribute> ne = attributes.getAll();
+
+					while(ne != null && ne.hasMoreElements())
+					{
+						javax.naming.directory.Attribute ldapAtt = ne.next();
+						if(passwordAttributeID.equalsIgnoreCase(ldapAtt.getID()))
+						{
+							Object thePass = ldapAtt.get();
+							setPasswordCallbackValue(thePass, passwdCallback);
+						}
+					} 
+				}       
+			}
+			finally
+			{
+				safeClose(results);
+				safeClose(ctx);
+				if (currentTCCL != null)
+					SecurityActions.setContextClassLoader(currentTCCL);
+			}            
+		}
+		catch(NamingException ne)
+		{
+			log.error(ne);
+			return;
+		} 
+		results = null;
+	}
+	
+	protected void verifyPassword( VerifyPasswordCallback vpc) throws NamingException
+	{
+		String credential = vpc.getValue();
+
+		ClassLoader currentTCCL = SecurityActions.getContextClassLoader();
+		if (currentTCCL != null)
+			SecurityActions.setContextClassLoader(null);
+
+		String  baseDN = (String) options.get(BASE_CTX_DN);
+		String  baseFilter = (String) options.get(BASE_FILTER_OPT);
+
+		InitialLdapContext ctx= this.constructInitialLdapContext(bindDN, bindCredential);
+		bindDNAuthentication(ctx, userName, credential, baseDN, baseFilter);
+		vpc.setVerified(true);
+	}
+	
+	protected String getBindDN()
+	{
+		String bindDN = (String) options.get(BIND_DN);
+		if(bindDN == null || bindDN.length() == 0)
+		{
+			if(trace)
+				log.trace("bindDN is not found");
+		}
+		return bindDN;
+	}
+	
+	protected String getBindCredential()
+	{
+		String bindCredential = (String) options.get(BIND_CREDENTIAL);
+		if (bindCredential.startsWith("{EXT}"))
+		{
+			try
+			{
+				bindCredential = new String(org.jboss.security.Util.loadPassword(bindCredential));
+			}
+			catch (Exception e1)
+			{
+				log.error("Exception in decrypting bindCredential:",e1);
+			}			
+		}
+		String securityDomain = (String) options.get(SECURITY_DOMAIN_OPT);
+		if (securityDomain != null)
+		{
+			try
+			{
+				ObjectName serviceName = new ObjectName(securityDomain);
+				char[] tmp = DecodeAction.decode(bindCredential, serviceName);
+				bindCredential = new String(tmp);
+			} 
+			catch (Exception e)
+			{
+				log.error("Exception in decrypting bindCredential:",e);
+			}
+		}
+		return bindCredential;
+	}
+	
+	protected void setPasswordCallbackValue(Object thePass, PasswordCallback passwdCallback)
+	{ 
+		String tmp = null;
+		if(thePass instanceof String)
+		{
+		    tmp = (String) thePass;
+			passwdCallback.setPassword(tmp.toCharArray());
+		}
+		else if(thePass instanceof char[])
+		{
+			passwdCallback.setPassword((char[])thePass); 
+		} 
+		else if(thePass instanceof byte[])
+		{
+			byte[] theBytes = (byte[]) thePass;
+			passwdCallback.setPassword((new String(theBytes).toCharArray())); 
+		} 
+		else
+		{
+			throw new RuntimeException(ErrorCodes.WRONG_TYPE + "password type:" + thePass.getClass());
+		}
+	}
+	
+	private InitialLdapContext constructInitialLdapContext(String dn, Object credential) throws NamingException
+	{
+		Properties env = new Properties();
+		Iterator<Entry<String, String>> iter = options.entrySet().iterator();
+		while (iter.hasNext())
+		{
+			Entry<String, String> entry = iter.next();
+			env.put(entry.getKey(), entry.getValue());
+		}
+
+		// 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 = (String) options.get(Context.PROVIDER_URL);
+		if (providerURL == null)
+			providerURL = "ldap://localhost:" + ((protocol != null && protocol.equals("ssl")) ? "636" : "389");
+
+		env.setProperty(Context.PROVIDER_URL, providerURL);
+		
+		distinguishedNameAttribute = (String) options.get(DISTINGUISHED_NAME_ATTRIBUTE_OPT);
+	      if (distinguishedNameAttribute == null)
+	          distinguishedNameAttribute = "distinguishedName";
+
+	      
+		// JBAS-3555, allow anonymous login with no bindDN and bindCredential
+		if (dn != null)
+			env.setProperty(Context.SECURITY_PRINCIPAL, dn);
+		if (credential != null)
+			env.put(Context.SECURITY_CREDENTIALS, credential);
+		traceLdapEnv(env);
+		return new InitialLdapContext(env, null);
+	}
+	
+	/**
+    @param ctx - the context to search from
+    @param user - the input username
+    @param credential - the bind credential
+    @param baseDN - base DN to search the ctx from
+    @param filter - the search filter string
+    @return the userDN string for the successful authentication 
+    @throws NamingException
+    */ 
+   @SuppressWarnings("rawtypes")
+   protected String bindDNAuthentication(InitialLdapContext ctx, String user, Object credential, String baseDN,
+         String filter) throws NamingException
+   {
+      SearchControls constraints = new SearchControls();
+      constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
+      constraints.setTimeLimit(searchTimeLimit);
+      String attrList[] = {distinguishedNameAttribute};
+      constraints.setReturningAttributes(attrList);
+
+      NamingEnumeration results = null;
+
+      Object[] filterArgs = {user};
+      results = ctx.search(baseDN, filter, filterArgs, constraints);
+      if (results.hasMore() == false)
+      {
+         results.close();
+         throw new NamingException(ErrorCodes.PROCESSING_FAILED + "Search of baseDN(" + baseDN + ") found no matches");
+      }
+
+      SearchResult sr = (SearchResult) results.next();
+      String name = sr.getName();
+      String userDN = null;
+      Attributes attrs = sr.getAttributes();
+      if (attrs != null)
+      {
+          Attribute dn = attrs.get(distinguishedNameAttribute);
+          if (dn != null)
+          {
+                  userDN = (String) dn.get();
+          }
+      }
+      if (userDN == null)
+      {
+          if (sr.isRelative() == true)
+                  userDN = name + ("".equals(baseDN) ? "" : "," + baseDN);
+          else
+                  throw new NamingException(ErrorCodes.PROCESSING_FAILED + "Can't follow referal for authentication: " + name);
+      }
+
+      safeClose(results);
+      results = null;
+      
+      InitialLdapContext userCtx = constructInitialLdapContext(userDN, credential);
+      safeClose(userCtx);
+      
+      return userDN;
+   }
+
+	private void traceLdapEnv(Properties env)
+	{
+		if (trace)
+		{
+			Properties tmp = new Properties();
+			tmp.putAll(env);
+			tmp.setProperty(Context.SECURITY_CREDENTIALS, "***");
+			log.trace("Logging into LDAP server, env=" + tmp.toString());
+		}
+	}
+	
+	@SuppressWarnings("rawtypes")
+	protected void safeClose(NamingEnumeration results)
+	{
+		if(results != null)
+		{
+			try 
+			{
+				results.close();
+			} catch (NamingException e) {}
+		}
+	}
+	
+	protected void safeClose(InitialLdapContext ic)
+	{
+		if(ic != null)
+		{
+			try 
+			{
+				ic.close();
+			} 
+			catch (NamingException e) 
+			{ 
+			}
+		}
+	}
+}
\ No newline at end of file

Added: trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/VerifyPasswordCallback.java
===================================================================
--- trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/VerifyPasswordCallback.java	                        (rev 0)
+++ trunk/security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/callback/VerifyPasswordCallback.java	2011-11-02 15:23:00 UTC (rev 280)
@@ -0,0 +1,66 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2011, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * 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.jboss.security.auth.callback;
+
+import javax.security.auth.callback.Callback;
+
+/**
+ * A {@code Callback} that indicates that the enclosing
+ * value needs to be checked against the value stored in
+ * the data store such as a DB or LDAP
+ * @author Anil Saldhana
+ * @since Nov 1, 2011
+ */
+public class VerifyPasswordCallback implements Callback 
+{
+	protected String value;
+	
+	protected boolean verified = false;
+
+	public String getValue() 
+	{
+		return value;
+	}
+
+	public void setValue(String value) 
+	{
+		this.value = value;
+	}
+	
+	public void setValue(char[] value)
+	{
+		this.value = new String(value);
+	}
+
+	public void setValue(byte[] value)
+	{
+		this.value = new String(value);
+	}
+
+	public boolean isVerified() {
+		return verified;
+	}
+
+	public void setVerified(boolean verified) {
+		this.verified = verified;
+	}
+}
\ No newline at end of file

Modified: trunk/security-jboss-sx/jbosssx/src/test/java/org/jboss/test/authentication/cbh/DatabaseCallbackHandlerUnitTestCase.java
===================================================================
--- trunk/security-jboss-sx/jbosssx/src/test/java/org/jboss/test/authentication/cbh/DatabaseCallbackHandlerUnitTestCase.java	2011-11-01 20:48:30 UTC (rev 279)
+++ trunk/security-jboss-sx/jbosssx/src/test/java/org/jboss/test/authentication/cbh/DatabaseCallbackHandlerUnitTestCase.java	2011-11-02 15:23:00 UTC (rev 280)
@@ -24,7 +24,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import java.io.IOException;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.PreparedStatement;
@@ -36,10 +38,12 @@
 import javax.security.auth.callback.Callback;
 import javax.security.auth.callback.NameCallback;
 import javax.security.auth.callback.PasswordCallback;
- 
+import javax.security.auth.login.CredentialException;
+
 import org.jboss.security.auth.callback.DatabaseCallbackHandler;
+import org.jboss.security.auth.callback.VerifyPasswordCallback;
 import org.junit.Before;
-import org.junit.Test; 
+import org.junit.Test;
 
 /**
  * Unit test the {@code DatabaseCallbackHandler}
@@ -126,6 +130,61 @@
 		assertNotSame("anilpass", new String(pcb.getPassword()));
 	}
 	
+	@Test
+	public void testVerifyPasswordCallbackPass() throws Exception
+	{
+		query();
+		DatabaseCallbackHandler cbh = new DatabaseCallbackHandler();
+		
+		Map<String,String> map = new HashMap<String,String>();
+        map.put(DatabaseCallbackHandler.DB_DRIVERNAME, driverName);
+        map.put(DatabaseCallbackHandler.CONNECTION_URL, connectionURL);
+        map.put(DatabaseCallbackHandler.DB_USERNAME, "sa");
+        map.put(DatabaseCallbackHandler.DB_USERPASS, "");
+        
+        cbh.setConfiguration(map);
+        
+		NameCallback ncb = new NameCallback("Enter");
+		ncb.setName("anil");
+		
+		VerifyPasswordCallback vpc = new VerifyPasswordCallback();
+		vpc.setValue("anilpass");
+		
+		cbh.handle(new Callback[] {ncb,vpc} );
+	}
+	
+	@Test
+	public void testVerifyPasswordCallbackFail() throws Exception
+	{
+		query();
+		DatabaseCallbackHandler cbh = new DatabaseCallbackHandler();
+		
+		Map<String,String> map = new HashMap<String,String>();
+        map.put(DatabaseCallbackHandler.DB_DRIVERNAME, driverName);
+        map.put(DatabaseCallbackHandler.CONNECTION_URL, connectionURL);
+        map.put(DatabaseCallbackHandler.DB_USERNAME, "sa");
+        map.put(DatabaseCallbackHandler.DB_USERPASS, "");
+        
+        cbh.setConfiguration(map);
+        
+		NameCallback ncb = new NameCallback("Enter");
+		ncb.setName("anil");
+		
+		VerifyPasswordCallback vpc = new VerifyPasswordCallback();
+		vpc.setValue("bad");
+		
+		try
+		{
+			cbh.handle(new Callback[] {ncb,vpc} );
+			fail("no exception thrown");
+		}
+		catch(IOException ie)
+		{
+			Throwable cause = ie.getCause();
+			assertTrue( cause instanceof CredentialException);
+		}
+	}
+	
 	private void query() throws Exception
 	{
 		Connection conn = getConnection();

Added: trunk/security-jboss-sx/jbosssx/src/test/java/org/jboss/test/authentication/cbh/LdapCallbackHandlerUnitTestCase.java
===================================================================
--- trunk/security-jboss-sx/jbosssx/src/test/java/org/jboss/test/authentication/cbh/LdapCallbackHandlerUnitTestCase.java	                        (rev 0)
+++ trunk/security-jboss-sx/jbosssx/src/test/java/org/jboss/test/authentication/cbh/LdapCallbackHandlerUnitTestCase.java	2011-11-02 15:23:00 UTC (rev 280)
@@ -0,0 +1,121 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2011, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * 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.jboss.test.authentication.cbh;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.naming.AuthenticationException;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.NameCallback;
+
+import org.jboss.security.auth.callback.LdapCallbackHandler;
+import org.jboss.security.auth.callback.VerifyPasswordCallback;
+import org.jboss.test.security.ldap.OpenDSUnitTestsAdapter;
+import org.junit.Test;
+
+/**
+ * Unit test the {@code LdapCallbackHandler}
+ * @author Anil Saldhana
+ * @since Oct 31, 2011
+ */
+public class LdapCallbackHandlerUnitTestCase extends OpenDSUnitTestsAdapter
+{
+	public LdapCallbackHandlerUnitTestCase(String name) 
+	{
+		super(name);
+	}
+	
+	protected void setUp() throws Exception
+	{
+		super.setUp();
+
+		//Let us add the ldapAttributes.ldif
+		String fileName = targetDir + "ldap" + fs + "ldapAttributes.ldif";
+		boolean op = util.addLDIF(serverHost, port, adminDN, adminPW, new File(fileName).toURI().toURL());
+		assertTrue(op);
+	}
+	
+	@Test
+	public void testSuccessfulCBH() throws Exception
+	{
+		LdapCallbackHandler cbh = new LdapCallbackHandler();
+		
+		Map<String,String> map = new HashMap<String,String>();
+		map.put("bindDN", "cn=Directory Manager");
+		map.put("bindCredential", "password");
+		map.put("baseFilter", "(uid={0})");
+		map.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
+		map.put("java.naming.provider.url", "ldap://localhost:10389");
+	    map.put("baseCtxDN", "ou=People,dc=jboss,dc=org");
+        
+        cbh.setConfiguration(map);
+        
+		NameCallback ncb = new NameCallback("Enter");
+		ncb.setName("jduke");
+		
+		VerifyPasswordCallback vpc = new VerifyPasswordCallback();
+		vpc.setValue("theduke");
+		 
+		cbh.handle(new Callback[] {ncb,vpc} );
+		
+		assertTrue(vpc.isVerified());
+	}
+	
+	@Test
+	public void testFailCBH() throws Exception
+	{
+		LdapCallbackHandler cbh = new LdapCallbackHandler();
+		
+		Map<String,String> map = new HashMap<String,String>();
+		map.put("bindDN", "cn=Directory Manager");
+		map.put("bindCredential", "password");
+		map.put("baseFilter", "(uid={0})");
+		map.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
+		map.put("java.naming.provider.url", "ldap://localhost:10389");
+	    map.put("baseCtxDN", "ou=People,dc=jboss,dc=org");
+        
+        cbh.setConfiguration(map);
+        
+		NameCallback ncb = new NameCallback("Enter");
+		ncb.setName("jduke");
+		
+		VerifyPasswordCallback vpc = new VerifyPasswordCallback();
+		vpc.setValue("badDUDE");
+		 
+		try
+		{
+			cbh.handle(new Callback[] {ncb,vpc} );
+			fail("should have thrown ex");
+		}
+		catch(IOException ae)
+		{
+			Throwable cause = ae.getCause();
+			assertNotNull(cause);
+			assertTrue(cause instanceof AuthenticationException);
+		}
+		
+		assertFalse(vpc.isVerified());
+	}
+}
\ No newline at end of file



More information about the jboss-cvs-commits mailing list