/* See LICENSE for licensing and NOTICE for copyright. */ package org.ldaptive.jaas; import java.security.Principal; import java.security.acl.Group; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import org.ldaptive.AbstractTest; import org.ldaptive.AttributeModification; import org.ldaptive.AttributeModificationType; import org.ldaptive.Connection; import org.ldaptive.DnParser; import org.ldaptive.LdapAttribute; import org.ldaptive.LdapEntry; import org.ldaptive.LdapException; import org.ldaptive.ModifyOperation; import org.ldaptive.ModifyRequest; import org.ldaptive.ResultCode; import org.ldaptive.TestUtils; import org.testng.AssertJUnit; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Parameters; import org.testng.annotations.Test; /** * Unit test for {@link LdapLoginModule}. * * @author Middleware Services */ public class LdapLoginModuleTest extends AbstractTest { /** Invalid password test data. */ public static final String INVALID_PASSWD = "not-a-password"; /** Entry created for auth tests. */ private static LdapEntry testLdapEntry; /** Entries for group tests. */ private static final Map<String, LdapEntry[]> GROUP_ENTRIES = new HashMap<>(); /** * Initialize the map of group entries. */ static { for (int i = 6; i <= 9; i++) { GROUP_ENTRIES.put(String.valueOf(i), new LdapEntry[2]); } } /** * @param ldifFile to create. * * @throws Exception On test failure. */ @Parameters("createEntry10") @BeforeClass(groups = {"jaas", "jaasInit"}) public void createAuthEntry(final String ldifFile) throws Exception { final String ldif = TestUtils.readFileIntoString(ldifFile); testLdapEntry = TestUtils.convertLdifToResult(ldif).getEntry(); super.createLdapEntry(testLdapEntry); System.setProperty("java.security.auth.login.config", "target/test-classes/ldap_jaas.config"); } /** * @param ldifFile6 to create. * @param ldifFile7 to create. * @param ldifFile8 to create. * @param ldifFile9 to create. * * @throws Exception On test failure. */ @Parameters( { "createGroup6", "createGroup7", "createGroup8", "createGroup9" }) @BeforeClass(groups = {"jaas"}, dependsOnGroups = {"jaasInit"}) public void createGroupEntry( final String ldifFile6, final String ldifFile7, final String ldifFile8, final String ldifFile9) throws Exception { // CheckStyle:Indentation OFF GROUP_ENTRIES.get("6")[0] = TestUtils.convertLdifToResult(TestUtils.readFileIntoString(ldifFile6)).getEntry(); GROUP_ENTRIES.get("7")[0] = TestUtils.convertLdifToResult(TestUtils.readFileIntoString(ldifFile7)).getEntry(); GROUP_ENTRIES.get("8")[0] = TestUtils.convertLdifToResult(TestUtils.readFileIntoString(ldifFile8)).getEntry(); GROUP_ENTRIES.get("9")[0] = TestUtils.convertLdifToResult(TestUtils.readFileIntoString(ldifFile9)).getEntry(); // CheckStyle:Indentation ON for (Map.Entry<String, LdapEntry[]> e : GROUP_ENTRIES.entrySet()) { super.createLdapEntry(e.getValue()[0]); } // setup group relationships try (Connection conn = TestUtils.createSetupConnection()) { conn.open(); final ModifyOperation modify = new ModifyOperation(conn); try { modify.execute( new ModifyRequest( GROUP_ENTRIES.get("6")[0].getDn(), new AttributeModification( AttributeModificationType.ADD, new LdapAttribute( "member", "cn=John Tyler," + DnParser.substring(testLdapEntry.getDn(), 1), "cn=Group 7," + DnParser.substring(testLdapEntry.getDn(), 1))))); } catch (LdapException e) { // ignore attribute already exists if (ResultCode.ATTRIBUTE_OR_VALUE_EXISTS != e.getResultCode()) { throw e; } } try { modify.execute( new ModifyRequest( GROUP_ENTRIES.get("7")[0].getDn(), new AttributeModification( AttributeModificationType.ADD, new LdapAttribute( "member", "cn=Group 8," + DnParser.substring(testLdapEntry.getDn(), 1), "cn=Group 9," + DnParser.substring(testLdapEntry.getDn(), 1))))); } catch (LdapException e) { // ignore attribute already exists if (ResultCode.ATTRIBUTE_OR_VALUE_EXISTS != e.getResultCode()) { throw e; } } try { modify.execute( new ModifyRequest( GROUP_ENTRIES.get("8")[0].getDn(), new AttributeModification( AttributeModificationType.ADD, new LdapAttribute("member", "cn=Group 7," + DnParser.substring(testLdapEntry.getDn(), 1))))); } catch (LdapException e) { // ignore attribute already exists if (ResultCode.ATTRIBUTE_OR_VALUE_EXISTS != e.getResultCode()) { throw e; } } } } /** @throws Exception On test failure. */ @AfterClass(groups = {"jaas"}) public void deleteAuthEntry() throws Exception { System.clearProperty("java.security.auth.login.config"); super.deleteLdapEntry(testLdapEntry.getDn()); super.deleteLdapEntry(GROUP_ENTRIES.get("6")[0].getDn()); super.deleteLdapEntry(GROUP_ENTRIES.get("7")[0].getDn()); super.deleteLdapEntry(GROUP_ENTRIES.get("8")[0].getDn()); super.deleteLdapEntry(GROUP_ENTRIES.get("9")[0].getDn()); try { PropertiesAuthenticatorFactory.close(); } catch (UnsupportedOperationException e) { // ignore if not supported AssertJUnit.assertNotNull(e); } try { PropertiesRoleResolverFactory.close(); } catch (UnsupportedOperationException e) { // ignore if not supported AssertJUnit.assertNotNull(e); } try { SpringAuthenticatorFactory.close(); } catch (UnsupportedOperationException e) { // ignore if not supported AssertJUnit.assertNotNull(e); } } /** * @param dn of this user * @param user to authenticate. * @param role to set for this user * @param credential to authenticate with. * * @throws Exception On test failure. */ @Parameters({ "jaasDn", "jaasUser", "jaasUserRole", "jaasCredential" }) @Test( groups = {"jaas"}, threadPoolSize = TEST_THREAD_POOL_SIZE, invocationCount = TEST_INVOCATION_COUNT, timeOut = TEST_TIME_OUT) public void contextTest(final String dn, final String user, final String role, final String credential) throws Exception { doContextTest("ldaptive", dn, user, role, credential, false); } /** * @param dn of this user * @param user to authenticate. * @param role to set for this user * @param credential to authenticate with. * * @throws Exception On test failure. */ @Parameters({ "jaasDn", "jaasUser", "jaasUserRole", "jaasCredential" }) @Test( groups = {"jaas"}, threadPoolSize = TEST_THREAD_POOL_SIZE, invocationCount = TEST_INVOCATION_COUNT, timeOut = TEST_TIME_OUT) public void contextSslTest(final String dn, final String user, final String role, final String credential) throws Exception { doContextTest("ldaptive-ssl", dn, user, role, credential, false); } /** * @param dn of this user * @param user to authenticate. * @param role to set for this user * @param credential to authenticate with. * * @throws Exception On test failure. */ @Parameters({ "jaasDn", "jaasUser", "jaasUserRole", "jaasCredential" }) @Test( groups = {"jaas"}, threadPoolSize = TEST_THREAD_POOL_SIZE, invocationCount = TEST_INVOCATION_COUNT, timeOut = TEST_TIME_OUT) public void randomContextTest(final String dn, final String user, final String role, final String credential) throws Exception { doContextTest("ldaptive-random", dn, user, role, credential, true); } /** * @param dn of this user * @param user to authenticate. * @param credential to authenticate with. * * @throws Exception On test failure. */ @Parameters({ "jaasDn", "jaasUser", "jaasCredential" }) @Test( groups = {"jaas"}, threadPoolSize = TEST_THREAD_POOL_SIZE, invocationCount = TEST_INVOCATION_COUNT, timeOut = TEST_TIME_OUT) public void pooledDnResolverContextTest(final String dn, final String user, final String credential) throws Exception { doContextTest("ldaptive-pooled-dnr", dn, user, "", credential, false); } /** * @param dn of this user * @param user to authenticate. * @param credential to authenticate with. * * @throws Exception On test failure. */ @Parameters({ "jaasDn", "jaasUser", "jaasCredential" }) @Test( groups = {"jaas"}, threadPoolSize = TEST_THREAD_POOL_SIZE, invocationCount = TEST_INVOCATION_COUNT, timeOut = TEST_TIME_OUT) public void springPooledDnResolverContextTest(final String dn, final String user, final String credential) throws Exception { doContextTest("ldaptive-pooled-dnr-spring", dn, user, "", credential, false); } /** * @param dn of this user * @param user to authenticate. * @param role to set for this user * @param credential to authenticate with. * * @throws Exception On test failure. */ @Parameters({ "jaasDn", "jaasUser", "jaasRoleCombined", "jaasCredential" }) @Test( groups = {"jaas"}, threadPoolSize = TEST_THREAD_POOL_SIZE, invocationCount = TEST_INVOCATION_COUNT, timeOut = TEST_TIME_OUT) public void rolesContextTest(final String dn, final String user, final String role, final String credential) throws Exception { doContextTest("ldaptive-roles", dn, user, role, credential, false); } /** * @param dn of this user * @param user to authenticate. * @param role to set for this user * @param credential to authenticate with. * * @throws Exception On test failure. */ @Parameters({ "jaasDn", "jaasUser", "jaasRoleCombinedRecursive", "jaasCredential" }) @Test(groups = {"jaas"}) public void rolesRecursiveContextTest(final String dn, final String user, final String role, final String credential) throws Exception { doContextTest("ldaptive-roles-recursive", dn, user, role, credential, false); } /** * @param dn of this user * @param user to authenticate. * @param role to set for this user * @param credential to authenticate with. * * @throws Exception On test failure. */ @Parameters({ "jaasDn", "jaasUser", "jaasUserRoleDefault", "jaasCredential" }) @Test(groups = {"jaas"}) public void useFirstContextTest(final String dn, final String user, final String role, final String credential) throws Exception { doContextTest("ldaptive-use-first", dn, user, role, credential, false); } /** * @param dn of this user * @param user to authenticate. * @param role to set for this user * @param credential to authenticate with. * * @throws Exception On test failure. */ @Parameters({ "jaasDn", "jaasUser", "jaasRoleCombined", "jaasCredential" }) @Test(groups = {"jaas"}) public void tryFirstContextTest(final String dn, final String user, final String role, final String credential) throws Exception { doContextTest("ldaptive-try-first", dn, user, role, credential, false); } /** * @param dn of this user * @param user to authenticate. * @param role to set for this user * @param credential to authenticate with. * * @throws Exception On test failure. */ @Parameters({ "jaasDn", "jaasUser", "jaasUserRole", "jaasCredential" }) @Test(groups = {"jaas"}) public void sufficientContextTest(final String dn, final String user, final String role, final String credential) throws Exception { doContextTest("ldaptive-sufficient", dn, user, role, credential, false); } /** * @param name of the jaas configuration * @param dn of this user * @param user to authenticate. * @param role to set for this user * @param credential to authenticate with. * @param checkLdapDn whether to check the LdapDnPrincipal * * @throws Exception On test failure. */ private void doContextTest( final String name, final String dn, final String user, final String role, final String credential, final boolean checkLdapDn) throws Exception { final TestCallbackHandler callback = new TestCallbackHandler(); callback.setName(user); callback.setPassword(INVALID_PASSWD); LoginContext lc = new LoginContext(name, callback); try { lc.login(); AssertJUnit.fail("Invalid password, login should have failed"); } catch (UnsupportedOperationException e) { throw e; } catch (Exception e) { AssertJUnit.assertEquals(e.getClass(), LoginException.class); } callback.setPassword(credential); lc = new LoginContext(name, callback); try { lc.login(); } catch (UnsupportedOperationException e) { throw e; } catch (Exception e) { AssertJUnit.fail(e.getMessage()); } final Set<LdapPrincipal> principals = lc.getSubject().getPrincipals(LdapPrincipal.class); AssertJUnit.assertEquals(1, principals.size()); final LdapPrincipal p = principals.iterator().next(); AssertJUnit.assertEquals(p.getName(), user); if (!"".equals(role)) { AssertJUnit.assertTrue(p.getLdapEntry().getAttributes().size() > 0); } final Set<LdapDnPrincipal> dnPrincipals = lc.getSubject().getPrincipals(LdapDnPrincipal.class); if (checkLdapDn) { AssertJUnit.assertEquals(1, dnPrincipals.size()); final LdapDnPrincipal dnP = dnPrincipals.iterator().next(); AssertJUnit.assertEquals(dnP.getName().toLowerCase(), dn.toLowerCase()); if (!"".equals(role)) { AssertJUnit.assertTrue(dnP.getLdapEntry().getAttributes().size() > 0); } } else { AssertJUnit.assertEquals(0, dnPrincipals.size()); } final Set<LdapRole> roles = lc.getSubject().getPrincipals(LdapRole.class); final Iterator<LdapRole> roleIter = roles.iterator(); String[] checkRoles = role.split("\\|"); if (checkRoles.length == 1 && "".equals(checkRoles[0])) { checkRoles = new String[0]; } AssertJUnit.assertEquals(checkRoles.length, roles.size()); while (roleIter.hasNext()) { final LdapRole r = roleIter.next(); boolean match = false; for (String s : checkRoles) { if (s.equals(r.getName())) { match = true; } } AssertJUnit.assertTrue(match); } final Set<LdapCredential> credentials = lc.getSubject().getPrivateCredentials(LdapCredential.class); AssertJUnit.assertEquals(1, credentials.size()); final LdapCredential c = credentials.iterator().next(); AssertJUnit.assertEquals(new String((char[]) c.getCredential()), credential); try { lc.logout(); } catch (Exception e) { AssertJUnit.fail(e.getMessage()); } AssertJUnit.assertEquals(0, lc.getSubject().getPrincipals().size()); AssertJUnit.assertEquals(0, lc.getSubject().getPrivateCredentials().size()); } /** * @param dn of this user * @param user to authenticate. * @param role to set for this user * * @throws Exception On test failure. */ @Parameters({ "jaasDn", "jaasUser", "jaasRoleCombined" }) @Test( groups = {"jaas"}, threadPoolSize = TEST_THREAD_POOL_SIZE, invocationCount = TEST_INVOCATION_COUNT, timeOut = TEST_TIME_OUT) public void rolesOnlyContextTest(final String dn, final String user, final String role) throws Exception { doRolesContextTest("ldaptive-roles-only", dn, user, role); } /** * @param dn of this user * @param user to authenticate. * @param role to set for this user * * @throws Exception On test failure. */ @Parameters({ "jaasDn", "jaasUser", "jaasRoleCombined" }) @Test( groups = {"jaas"}, threadPoolSize = TEST_THREAD_POOL_SIZE, invocationCount = TEST_INVOCATION_COUNT, timeOut = TEST_TIME_OUT) public void dnRolesOnlyContextTest(final String dn, final String user, final String role) throws Exception { doRolesContextTest("ldaptive-dn-roles-only", dn, user, role); } /** * @param dn of this user * @param user to authenticate. * @param role to set for this user * * @throws Exception On test failure. */ @Parameters({ "jaasDn", "jaasUser", "jaasRoleCombined" }) @Test( groups = {"jaas"}, threadPoolSize = TEST_THREAD_POOL_SIZE, invocationCount = TEST_INVOCATION_COUNT, timeOut = TEST_TIME_OUT) public void dnRolesOnlyPooledContextTest(final String dn, final String user, final String role) throws Exception { doRolesContextTest("ldaptive-roles-only-pooled", dn, user, role); } /** * @param name of the jaas configuration * @param dn of this user * @param user to authenticate. * @param role to set for this user * * @throws Exception On test failure. */ private void doRolesContextTest(final String name, final String dn, final String user, final String role) throws Exception { final TestCallbackHandler callback = new TestCallbackHandler(); callback.setName(user); final LoginContext lc = new LoginContext(name, callback); try { lc.login(); } catch (UnsupportedOperationException e) { throw e; } catch (Exception e) { AssertJUnit.fail(e.getMessage()); } final Set<LdapRole> roles = lc.getSubject().getPrincipals(LdapRole.class); final Iterator<LdapRole> roleIter = roles.iterator(); final String[] checkRoles = role.split("\\|"); AssertJUnit.assertEquals(checkRoles.length, roles.size()); while (roleIter.hasNext()) { final LdapRole r = roleIter.next(); boolean match = false; for (String s : checkRoles) { if (s.equals(r.getName())) { match = true; } } AssertJUnit.assertTrue(match); } final Set<Group> roleGroups = lc.getSubject().getPrincipals(Group.class); AssertJUnit.assertTrue(roleGroups.size() == 2); for (Group g : roleGroups) { if ("Roles".equals(g.getName())) { final Enumeration<? extends Principal> members = g.members(); int count = 0; while (members.hasMoreElements()) { final Principal p = members.nextElement(); boolean match = false; for (LdapRole lr : lc.getSubject().getPrincipals(LdapRole.class)) { if (lr.getName().equals(p.getName())) { match = true; } } AssertJUnit.assertTrue(match); count++; } AssertJUnit.assertEquals(count, lc.getSubject().getPrincipals(LdapRole.class).size()); } else if ("Principals".equals(g.getName())) { final Enumeration<? extends Principal> members = g.members(); int count = 0; while (members.hasMoreElements()) { final Principal p = members.nextElement(); boolean match = false; for (LdapPrincipal lp : lc.getSubject().getPrincipals(LdapPrincipal.class)) { if (lp.getName().equals(p.getName())) { match = true; } } AssertJUnit.assertTrue(match); count++; } AssertJUnit.assertEquals(count, lc.getSubject().getPrincipals(LdapPrincipal.class).size()); } else { AssertJUnit.fail("Found invalid group"); } } final Set<?> credentials = lc.getSubject().getPrivateCredentials(); AssertJUnit.assertEquals(0, credentials.size()); try { lc.logout(); } catch (Exception e) { AssertJUnit.fail(e.getMessage()); } AssertJUnit.assertEquals(0, lc.getSubject().getPrincipals().size()); AssertJUnit.assertEquals(0, lc.getSubject().getPrivateCredentials().size()); } }