/*
* 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());
}
}
}