/* * JBoss, Home of Professional Open Source * Copyright 2007, 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.test.authentication.jaas; import java.lang.reflect.Method; import java.security.MessageDigest; import java.security.Principal; import java.security.acl.Group; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import junit.framework.TestCase; import org.jboss.crypto.CryptoUtil; import org.jboss.logging.Logger; import org.jboss.security.SecurityContextAssociation; import org.jboss.security.SimpleGroup; import org.jboss.security.SimplePrincipal; import org.jboss.security.auth.callback.UsernamePasswordHandler; import org.jboss.security.auth.spi.UsernamePasswordLoginModule; /** Tests of the LoginModule classes. * * ANIL: Not all the login modules are tested here. There is a larger * test case in AS trunk that tests most of the LMs * @author Scott.Stark@jboss.org * @version $Revision$ */ @SuppressWarnings("unchecked") public class LoginModulesUnitTestCase extends TestCase { private static Logger log = Logger.getLogger(LoginModulesUnitTestCase.class); /** Hard coded login configurations for the test cases. The configuration name corresponds to the unit test function that uses the configuration. */ static class TestConfig extends Configuration { @Override public void refresh() { } @Override public AppConfigurationEntry[] getAppConfigurationEntry(String name) { AppConfigurationEntry[] entry = null; try { Class[] parameterTypes = {}; Method m = getClass().getDeclaredMethod(name, parameterTypes); Object[] args = {}; entry = (AppConfigurationEntry[]) m.invoke(this, args); } catch(Exception e) { } return entry; } AppConfigurationEntry[] testClientLogin() { String name = "org.jboss.security.ClientLoginModule"; HashMap options = new HashMap(); options.put("restore-login-identity", "true"); AppConfigurationEntry ace = new AppConfigurationEntry(name, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); AppConfigurationEntry[] entry = {ace}; return entry; } AppConfigurationEntry[] testIdentity() { String name = "org.jboss.security.auth.spi.IdentityLoginModule"; HashMap options = new HashMap(); options.put("principal", "stark"); options.put("roles", "Role3,Role4"); AppConfigurationEntry ace = new AppConfigurationEntry(name, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); AppConfigurationEntry[] entry = {ace}; return entry; } AppConfigurationEntry[] testSimple() { String name = "org.jboss.security.auth.spi.SimpleServerLoginModule"; AppConfigurationEntry ace = new AppConfigurationEntry(name, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, new HashMap()); AppConfigurationEntry[] entry = {ace}; return entry; } AppConfigurationEntry[] testUsernamePassword() { return other(); } AppConfigurationEntry[] testAnon() { String name = "org.jboss.security.auth.spi.AnonLoginModule"; HashMap options = new HashMap(); options.put("unauthenticatedIdentity", "nobody"); AppConfigurationEntry ace = new AppConfigurationEntry(name, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); AppConfigurationEntry[] entry = {ace}; return entry; } AppConfigurationEntry[] testNull() { String name = "org.jboss.security.auth.spi.AnonLoginModule"; HashMap options = new HashMap(); AppConfigurationEntry ace = new AppConfigurationEntry(name, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); AppConfigurationEntry[] entry = {ace}; return entry; } AppConfigurationEntry[] testUsersRoles() { String name = "org.jboss.security.auth.spi.UsersRolesLoginModule"; HashMap options = new HashMap(); options.put("usersProperties", "security/users.properties"); options.put("rolesProperties", "security/roles.properties"); AppConfigurationEntry ace = new AppConfigurationEntry(name, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); AppConfigurationEntry[] entry = {ace}; return entry; } AppConfigurationEntry[] testSharedMap() { String name = "org.jboss.test.authentication.jaas.helpers.SharedStatePopulatingLoginModule"; HashMap options = new HashMap(); options.put("useFirstPass", "true"); String anothername = "org.jboss.test.authentication.jaas.helpers.SharedStateRetrievingLoginModule"; HashMap anotherOptions = new HashMap(); anotherOptions.put("username", "anil"); anotherOptions.put("password", "superman"); AppConfigurationEntry ace = new AppConfigurationEntry(name, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); AppConfigurationEntry anotherAce = new AppConfigurationEntry(anothername, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, anotherOptions); AppConfigurationEntry[] entry = {ace,anotherAce}; return entry; } /** * <p> * Obtains a configuration that uses a module that fails the validation phase. As the flag * {@code throwValidateError} is not set to true, the validation exception should be available to the test method. * </p> * * @return the test {@code AppConfigurationEntry}. */ AppConfigurationEntry[] testValidateError() { AppConfigurationEntry entry = new AppConfigurationEntry(ValidateErrorLoginModule.class.getName(), AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, new HashMap()); return new AppConfigurationEntry[] {entry}; } /** * <p> * Obtains a configuration that uses a module that fails the validation phase. As the flag * {@code throwValidateError} is set to true, the validation exception should available to the test method. * </p> * * @return the test {@code AppConfigurationEntry}. */ AppConfigurationEntry[] testValidateErrorWithFlag() { HashMap options = new HashMap(); options.put("throwValidateError", "true"); AppConfigurationEntry entry = new AppConfigurationEntry(ValidateErrorLoginModule.class.getName(), AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); return new AppConfigurationEntry[]{entry}; } /** * <p> * Obtains a configuration that uses a module in conjunction with an {@code InputValidator} to check if the * supplied username and password are valid. * </p> * * @return the test {@code AppConfigurationEntry}. */ AppConfigurationEntry[] testInputValidator() { HashMap options = new HashMap(); options.put("inputValidator", "org.jboss.test.authentication.jaas.helpers.TestInputValidator"); AppConfigurationEntry entry = new AppConfigurationEntry(TestLoginModule.class.getName(), AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); return new AppConfigurationEntry[]{entry}; } AppConfigurationEntry[] other() { AppConfigurationEntry ace = new AppConfigurationEntry(TestLoginModule.class.getName(), AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, new HashMap()); AppConfigurationEntry[] entry = {ace}; return entry; } } public static class TestLoginModule extends UsernamePasswordLoginModule { @Override protected Group[] getRoleSets() { SimpleGroup roles = new SimpleGroup("Roles"); Group[] roleSets = {roles}; roles.addMember(new SimplePrincipal("TestRole")); roles.addMember(new SimplePrincipal("Role2")); return roleSets; } /** This represents the 'true' password */ @Override protected String getUsersPassword() { return "secret"; } } public static class HashTestLoginModule extends TestLoginModule { /** This represents the 'true' password in its hashed form */ @Override protected String getUsersPassword() { MessageDigest md = null; try { md = MessageDigest.getInstance("MD5"); } catch(Exception e) { e.printStackTrace(); } byte[] passwordBytes = "secret".getBytes(); byte[] hash = md.digest(passwordBytes); String passwordHash = CryptoUtil.encodeBase64(hash); return passwordHash; } } public static class HashTestDigestCallbackLoginModule extends TestLoginModule { /** This represents the 'true' password in its hashed form */ @Override protected String getUsersPassword() { MessageDigest md = null; try { md = MessageDigest.getInstance("MD5"); } catch(Exception e) { e.printStackTrace(); } byte[] passwordBytes = "secret".getBytes(); md.update("pre".getBytes()); md.update(passwordBytes); md.update("post".getBytes()); byte[] hash = md.digest(); String passwordHash = CryptoUtil.encodeBase64(hash); return passwordHash; } } /** * <p> * Login module used in the throwValidateError tests. * </p> */ public static class ValidateErrorLoginModule extends TestLoginModule { @Override protected boolean validatePassword(String inputPassword, String expectedPassword) { // sets a validate error and returns false. super.setValidateError(new Exception("Validate Exception")); return false; } } public LoginModulesUnitTestCase(String testName) { super(testName); } @Override protected void setUp() throws Exception { // Install the custom JAAS configuration Configuration.setConfiguration(new TestConfig()); super.setUp(); } public void testClientLogin() throws Exception { log.info("testClientLogin"); UsernamePasswordHandler handler = new UsernamePasswordHandler("scott", "secret".toCharArray()); LoginContext lc = new LoginContext("testClientLogin", handler); lc.login(); Subject subject = lc.getSubject(); Principal scott = new SimplePrincipal("scott"); assertTrue("Principals contains scott", subject.getPrincipals().contains(scott)); Principal saPrincipal = SecurityContextAssociation.getPrincipal(); assertTrue("SecurityAssociation.getPrincipal == scott", saPrincipal.equals(scott)); UsernamePasswordHandler handler2 = new UsernamePasswordHandler("scott2", "secret2".toCharArray()); LoginContext lc2 = new LoginContext("testClientLogin", handler2); lc2.login(); Principal scott2 = new SimplePrincipal("scott2"); saPrincipal = SecurityContextAssociation.getPrincipal(); assertTrue("SecurityAssociation.getPrincipal == scott2", saPrincipal.equals(scott2)); lc2.logout(); saPrincipal = SecurityContextAssociation.getPrincipal(); assertTrue("SecurityAssociation.getPrincipal == scott", saPrincipal.equals(scott)); lc.logout(); } public void testUsernamePassword() throws Exception { log.info("testUsernamePassword"); UsernamePasswordHandler handler = new UsernamePasswordHandler("scott", "secret".toCharArray()); LoginContext lc = new LoginContext("testUsernamePassword", handler); lc.login(); Subject subject = lc.getSubject(); Set<Group> groups = subject.getPrincipals(Group.class); Principal scott = new SimplePrincipal("scott"); assertTrue("Principals contains scott", subject.getPrincipals().contains(scott)); assertTrue("Principals contains Roles", groups.contains(new SimpleGroup("Roles"))); assertTrue("Principals contains CallerPrincipal", groups.contains(new SimpleGroup("CallerPrincipal"))); for (Group group : groups) { if (group.getName().equals("Roles")) { Enumeration<? extends Principal> roles = group.members(); assertEquals("Roles group has 2 entries", 2, Collections.list(roles).size()); assertTrue("TestRole is a role", group.isMember(new SimplePrincipal("TestRole"))); assertTrue("Role2 is a role", group.isMember(new SimplePrincipal("Role2"))); } else if (group.getName().equals("CallerPrincipal")) { Enumeration<? extends Principal> roles = group.members(); assertEquals("CallerPrincipal group has 1 entry", 1, Collections.list(roles).size()); assertTrue("scott is the caller principal", group.isMember(scott)); } else { fail("Another group was set: " + group.getName()); } } lc.logout(); } public void testUsernamePasswordHash() throws Exception { log.info("testUsernamePasswordHash"); UsernamePasswordHandler handler = new UsernamePasswordHandler("scott", "secret".toCharArray()); LoginContext lc = new LoginContext("testUsernamePasswordHash", handler); lc.login(); Subject subject = lc.getSubject(); Set<Group> groups = subject.getPrincipals(Group.class); Principal scott = new SimplePrincipal("scott"); assertTrue("Principals contains scott", subject.getPrincipals().contains(scott)); assertTrue("Principals contains Roles", groups.contains(new SimpleGroup("Roles"))); assertTrue("Principals contains CallerPrincipal", groups.contains(new SimpleGroup("CallerPrincipal"))); for (Group group : groups) { if (group.getName().equals("Roles")) { Enumeration<? extends Principal> roles = group.members(); assertEquals("Roles group has 2 entries", 2, Collections.list(roles).size()); assertTrue("TestRole is a role", group.isMember(new SimplePrincipal("TestRole"))); assertTrue("Role2 is a role", group.isMember(new SimplePrincipal("Role2"))); } else if (group.getName().equals("CallerPrincipal")) { Enumeration<? extends Principal> roles = group.members(); assertEquals("CallerPrincipal group has 1 entry", 1, Collections.list(roles).size()); assertTrue("scott is the caller principal", group.isMember(scott)); } else { fail("Another group was set: " + group.getName()); } } lc.logout(); } public void testAnon() throws Exception { log.info("testAnon"); UsernamePasswordHandler handler = new UsernamePasswordHandler(null, null); LoginContext lc = new LoginContext("testAnon", handler); lc.login(); Subject subject = lc.getSubject(); Set<Group> groups = subject.getPrincipals(Group.class); Principal nobody = new SimplePrincipal("nobody"); assertTrue("Principals contains nobody", subject.getPrincipals().contains(nobody)); assertTrue("Principals contains Roles", groups.contains(new SimpleGroup("Roles"))); assertTrue("Principals contains CallerPrincipal", groups.contains(new SimpleGroup("CallerPrincipal"))); for (Group group : groups) { if (group.getName().equals("Roles")) { assertTrue("Roles has no members", !group.members().hasMoreElements()); } else if (group.getName().equals("CallerPrincipal")) { Enumeration<? extends Principal> roles = group.members(); assertEquals("CallerPrincipal group has 1 entry", 1, Collections.list(roles).size()); assertTrue("scott is the caller principal", group.isMember(nobody)); } else { fail("Another group was set: " + group.getName()); } } lc.logout(); } public void testNull() throws Exception { log.info("testNull"); UsernamePasswordHandler handler = new UsernamePasswordHandler(null, null); LoginContext lc = new LoginContext("testNull", handler); try { lc.login(); fail("Should not be able to login as null, null"); } catch(LoginException e) { // Ok } } public void testIdentity() throws Exception { log.info("testIdentity"); LoginContext lc = new LoginContext("testIdentity"); lc.login(); Subject subject = lc.getSubject(); Set<Group> groups = subject.getPrincipals(Group.class); Principal stark = new SimplePrincipal("stark"); assertTrue("Principals contains stark", subject.getPrincipals().contains(stark)); assertTrue("Principals contains Roles", groups.contains(new SimpleGroup("Roles"))); assertTrue("Principals contains CallerPrincipal", groups.contains(new SimpleGroup("CallerPrincipal"))); for (Group group : groups) { if (group.getName().equals("Roles")) { Enumeration<? extends Principal> roles = group.members(); assertEquals("Roles group has 2 entries", 2, Collections.list(roles).size()); assertTrue("Role2 is not a role", !group.isMember(new SimplePrincipal("Role2"))); assertTrue("Role3 is a role", group.isMember(new SimplePrincipal("Role3"))); assertTrue("Role4 is a role", group.isMember(new SimplePrincipal("Role4"))); } else if (group.getName().equals("CallerPrincipal")) { Enumeration<? extends Principal> roles = group.members(); assertEquals("CallerPrincipal group has 1 entry", 1, Collections.list(roles).size()); assertTrue("scott is the caller principal", group.isMember(stark)); } else { fail("Another group was set: " + group.getName()); } } lc.logout(); } public void testSimple() throws Exception { log.info("testSimple"); UsernamePasswordHandler handler = new UsernamePasswordHandler("jduke", "jduke".toCharArray()); LoginContext lc = new LoginContext("testSimple", handler); lc.login(); Subject subject = lc.getSubject(); Set<Group> groups = subject.getPrincipals(Group.class); Principal jduke = new SimplePrincipal("jduke"); assertTrue("Principals contains jduke", subject.getPrincipals().contains(jduke)); assertTrue("Principals contains Roles", groups.contains(new SimpleGroup("Roles"))); assertTrue("Principals contains CallerPrincipal", groups.contains(new SimpleGroup("CallerPrincipal"))); for (Group group : groups) { if (group.getName().equals("Roles")) { Enumeration<? extends Principal> roles = group.members(); assertEquals("Roles group has 2 entries", 2, Collections.list(roles).size()); assertTrue("user is a role", group.isMember(new SimplePrincipal("user"))); assertTrue("guest is a role", group.isMember(new SimplePrincipal("guest"))); } else if (group.getName().equals("CallerPrincipal")) { Enumeration<? extends Principal> roles = group.members(); assertEquals("CallerPrincipal group has 1 entry", 1, Collections.list(roles).size()); assertTrue("scott is the caller principal", group.isMember(jduke)); } else { fail("Another group was set: " + group.getName()); } } lc.logout(); } public void testSharedMap() throws Exception { log.info("testSharedMap"); UsernamePasswordHandler handler = new UsernamePasswordHandler("anil", "superman".toCharArray()); LoginContext lc = new LoginContext("testSharedMap", handler); lc.login(); Subject subject = lc.getSubject(); assertTrue("Principals contains jduke", subject.getPrincipals().contains(new SimplePrincipal("anil"))); lc.logout(); } /** * <p> * Tests the behavior of the {@code throwValidateError flag}. The test uses a login module that fails the validation * phase and sets a validation exception with an error message. In the first scenario, a configuration that doesn't * set the {@code throwValidateError} flag is used. As a result, the exception that is caught should not have any * cause set. In the second scenario, a configuration that sets the flag to {@code true} is used. As a result, the * exception that is caught should contain the validation exception as the root cause. * </p> * * @throws Exception if an error occurs while running the test. */ public void testValidateError() throws Exception { // test the configuration that doesn't set the throwValidateError flag. LoginContext context = new LoginContext("testValidateError", new UsernamePasswordHandler(null, null)); try { context.login(); fail("Login should have failed as the validation of the test module was unsuccessful"); } catch(LoginException le) { assertNull("Unexpected root throwable found", le.getCause()); } // test the configuration that sets the throwValidateError flag. context = new LoginContext("testValidateErrorWithFlag", new UsernamePasswordHandler(null, null)); try { context.login(); fail("Login should have failed as THE validation of the test module was unsuccessful"); } catch(LoginException le) { assertNotNull("Unexpected null root throwable", le.getCause()); assertEquals("Invalid root message", "Validate Exception", le.getCause().getMessage()); } } /** * <p> * Tests the usage of an {@code InputValidator} to verify that the client-supplied username and password * adhere to the expected rules. * </p> * * @throws Exception if an error occurs while running the test. */ public void testInputValidator() throws Exception { // let's start with a valid username/password pair. LoginContext context = new LoginContext("testInputValidator", new UsernamePasswordHandler("user", "secret")); context.login(); assertNotNull(context.getSubject()); context.logout(); // now let's try a username that doesn't conform to the [A-Za-z0-9]* pattern. context = new LoginContext("testInputValidator", new UsernamePasswordHandler("$user$", "secret")); try { context.login(); fail("Login should have failed as the supplied username does not adhere to the expected pattern"); } catch(LoginException le) { assertEquals("Username or password does not adhere to the acceptable pattern", le.getMessage()); } // now let's try a password that doesn't conform to the pattern by including a space in the middle of the password). context = new LoginContext("testInputValidator", new UsernamePasswordHandler("user", "sec ret")); try { context.login(); fail("Login should have failed as the supplied username does not adhere to the expected pattern"); } catch(LoginException le) { assertEquals("Username or password does not adhere to the acceptable pattern", le.getMessage()); } // finally, let's try a username that has one of the blacklisted tokens. context = new LoginContext("testInputValidator", new UsernamePasswordHandler("javaINSERTduke", "secret")); try { context.login(); fail("Login should have failed as the supplied username does not adhere to the expected pattern"); } catch(LoginException le) { assertEquals("Username or password contains invalid tokens", le.getMessage()); } } }