Repository: karaf Updated Branches: refs/heads/master 49ae2040c -> 5c6e71dce
[KARAF-3382] Add role mapping support in LDAP login module Project: http://git-wip-us.apache.org/repos/asf/karaf/repo Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/5c6e71dc Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/5c6e71dc Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/5c6e71dc Branch: refs/heads/master Commit: 5c6e71dce482f5628af202d3b3f8094a1aff0580 Parents: 49ae204 Author: Jean-Baptiste Onofré <[email protected]> Authored: Mon Dec 29 09:04:37 2014 +0100 Committer: Jean-Baptiste Onofré <[email protected]> Committed: Mon Dec 29 09:04:37 2014 +0100 ---------------------------------------------------------------------- .../jaas/modules/ldap/LDAPLoginModule.java | 57 ++++++++- .../jaas/modules/ldap/LdapLoginModuleTest.java | 115 +++++++++++++++++-- 2 files changed, 155 insertions(+), 17 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/karaf/blob/5c6e71dc/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPLoginModule.java ---------------------------------------------------------------------- diff --git a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPLoginModule.java b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPLoginModule.java index b865bc6..6b312c6 100644 --- a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPLoginModule.java +++ b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPLoginModule.java @@ -32,11 +32,7 @@ import javax.security.auth.callback.*; import javax.security.auth.login.LoginException; import java.io.IOException; import java.security.Principal; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.Callable; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -60,6 +56,7 @@ public class LDAPLoginModule extends AbstractKarafLoginModule { public final static String ROLE_FILTER = "role.filter"; public final static String ROLE_NAME_ATTRIBUTE = "role.name.attribute"; public final static String ROLE_SEARCH_SUBTREE = "role.search.subtree"; + public final static String ROLE_MAPPING = "role.mapping"; public final static String AUTHENTICATION = "authentication"; public final static String ALLOW_EMPTY_PASSWORDS = "allowEmptyPasswords"; public final static String INITIAL_CONTEXT_FACTORY = "initial.context.factory"; @@ -85,6 +82,7 @@ public class LDAPLoginModule extends AbstractKarafLoginModule { private String roleFilter; private String roleNameAttribute; private boolean roleSearchSubtree = true; + private Map<String, Set<String>> roleMapping; private String authentication = DEFAULT_AUTHENTICATION; private boolean allowEmptyPasswords = false; private String initialContextFactory = null; @@ -109,6 +107,7 @@ public class LDAPLoginModule extends AbstractKarafLoginModule { roleFilter = (String) options.get(ROLE_FILTER); roleNameAttribute = (String) options.get(ROLE_NAME_ATTRIBUTE); roleSearchSubtree = Boolean.parseBoolean((String) options.get(ROLE_SEARCH_SUBTREE)); + roleMapping = parseRoleMapping((String) options.get(ROLE_MAPPING)); initialContextFactory = (String) options.get(INITIAL_CONTEXT_FACTORY); if (initialContextFactory == null) { initialContextFactory = DEFAULT_INITIAL_CONTEXT_FACTORY; @@ -139,6 +138,27 @@ public class LDAPLoginModule extends AbstractKarafLoginModule { } } + private Map<String, Set<String>> parseRoleMapping(String option) { + Map<String, Set<String>> roleMapping = new HashMap<String, Set<String>>(); + if (option != null) { + logger.debug("Parse role mapping {}", option); + String[] mappings = option.split(";"); + for (String mapping : mappings) { + String[] map = mapping.split("=", 2); + String ldapRole = map[0]; + String[] karafRoles = map[1].split(","); + if (roleMapping.get(ldapRole) == null) { + roleMapping.put(ldapRole, new HashSet<String>()); + } + final Set<String> karafRolesSet = roleMapping.get(ldapRole); + for (String karafRole : karafRoles) { + karafRolesSet.add(karafRole); + } + } + } + return roleMapping; + } + public boolean login() throws LoginException { ClassLoader tccl = Thread.currentThread().getContextClassLoader(); try { @@ -342,7 +362,16 @@ public class LDAPLoginModule extends AbstractKarafLoginModule { for (int i = 0; i < roles.size(); i++) { String role = (String) roles.get(i); if (role != null) { - rolesList.add(role); + logger.debug("User {} is a member of role {}", user, role); + // handle role mapping + Set<String> roleMappings = tryMappingRole(role); + if (roleMappings.isEmpty()) { + rolesList.add(role); + } else { + for (String roleMapped : roleMappings) { + rolesList.add(roleMapped); + } + } } } } @@ -369,6 +398,22 @@ public class LDAPLoginModule extends AbstractKarafLoginModule { return true; } + protected Set<String> tryMappingRole(String role) { + Set<String> roles = new HashSet<String>(); + if (roleMapping == null || roleMapping.isEmpty()) { + return roles; + } + Set<String> karafRoles = roleMapping.get(role); + if (karafRoles != null) { + // add all mapped roles + for (String karafRole : karafRoles) { + logger.debug("LDAP role {} is mapped to Karaf role {}", role, karafRole); + roles.add(karafRole); + } + } + return roles; + } + protected void setupSsl(Hashtable env) throws LoginException { ServiceReference ref = null; try { http://git-wip-us.apache.org/repos/asf/karaf/blob/5c6e71dc/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LdapLoginModuleTest.java ---------------------------------------------------------------------- diff --git a/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LdapLoginModuleTest.java b/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LdapLoginModuleTest.java index 0f24705..b686f10 100644 --- a/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LdapLoginModuleTest.java +++ b/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LdapLoginModuleTest.java @@ -13,7 +13,6 @@ * limitations under the License. * under the License. */ - package org.apache.karaf.jaas.modules.ldap; import org.apache.directory.server.core.integ.AbstractLdapTestUnit; @@ -37,18 +36,21 @@ import javax.security.auth.login.LoginException; import java.io.File; import java.io.IOException; import java.security.Principal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; - -@RunWith ( FrameworkRunner.class ) -@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP", port=9999)}) +@RunWith(FrameworkRunner.class) +@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP", port = 9999)}) @CreateDS(name = "LdapLoginModuleTest-class", - partitions = { @CreatePartition(name = "example", suffix = "dc=example,dc=com") }) + partitions = {@CreatePartition(name = "example", suffix = "dc=example,dc=com")}) @ApplyLdifFiles( - "org/apache/karaf/jaas/modules/ldap/example.com.ldif" + "org/apache/karaf/jaas/modules/ldap/example.com.ldif" ) public class LdapLoginModuleTest extends AbstractLdapTestUnit { @@ -96,13 +98,13 @@ public class LdapLoginModuleTest extends AbstractLdapTestUnit { assertTrue(foundRole); assertTrue(module.logout()); - assertEquals("Principals should be gone as the user has logged out", 0, subject.getPrincipals().size()); + assertEquals("Principals should be gone as the user has logged out", 0, subject.getPrincipals().size()); } protected Properties ldapLoginModuleOptions() throws IOException { return new Properties(new File("src/test/resources/org/apache/karaf/jaas/modules/ldap/ldap.properties")); } - + @Test public void testNonAdminLogin() throws Exception { Properties options = ldapLoginModuleOptions(); @@ -143,9 +145,9 @@ public class LdapLoginModuleTest extends AbstractLdapTestUnit { assertFalse(foundRole); assertTrue(module.logout()); - assertEquals("Principals should be gone as the user has logged out", 0, subject.getPrincipals().size()); + assertEquals("Principals should be gone as the user has logged out", 0, subject.getPrincipals().size()); } - + @Test public void testBadPassword() throws Exception { Properties options = ldapLoginModuleOptions(); @@ -167,7 +169,7 @@ public class LdapLoginModuleTest extends AbstractLdapTestUnit { assertEquals("Precondition", 0, subject.getPrincipals().size()); assertFalse(module.login()); } - + @Test public void testUserNotFound() throws Exception { Properties options = ldapLoginModuleOptions(); @@ -216,5 +218,96 @@ public class LdapLoginModuleTest extends AbstractLdapTestUnit { assertTrue(e.getMessage().equals("Empty passwords not allowed")); } } + + @Test + public void testRoleMappingSimple() throws Exception { + Properties options = ldapLoginModuleOptions(); + options.put(LDAPLoginModule.ROLE_MAPPING, "admin=karaf"); + LDAPLoginModule module = new LDAPLoginModule(); + CallbackHandler cb = new CallbackHandler() { + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback cb : callbacks) { + if (cb instanceof NameCallback) { + ((NameCallback) cb).setName("admin"); + } else if (cb instanceof PasswordCallback) { + ((PasswordCallback) cb).setPassword("admin123".toCharArray()); + } + } + } + }; + Subject subject = new Subject(); + module.initialize(subject, cb, null, options); + + assertEquals("Precondition", 0, subject.getPrincipals().size()); + assertTrue(module.login()); + assertTrue(module.commit()); + + assertEquals(2, subject.getPrincipals().size()); + + boolean foundUser = false; + boolean foundRole = false; + for (Principal principal : subject.getPrincipals()) { + if (principal instanceof UserPrincipal) { + assertEquals("admin", principal.getName()); + foundUser = true; + } else if (principal instanceof RolePrincipal) { + assertEquals("karaf", principal.getName()); + foundRole = true; + } + } + assertTrue(foundUser); + assertTrue(foundRole); + + assertTrue(module.logout()); + assertEquals("Principals should be gone as the user has logged out", 0, subject.getPrincipals().size()); + } + + @Test + public void testRoleMappingAdvanced() throws Exception { + Properties options = ldapLoginModuleOptions(); + options.put(LDAPLoginModule.ROLE_MAPPING, "admin=karaf,test;admin=another"); + LDAPLoginModule module = new LDAPLoginModule(); + CallbackHandler cb = new CallbackHandler() { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback cb : callbacks) { + if (cb instanceof NameCallback) { + ((NameCallback) cb).setName("admin"); + } else if (cb instanceof PasswordCallback) { + ((PasswordCallback) cb).setPassword("admin123".toCharArray()); + } + } + } + }; + Subject subject = new Subject(); + module.initialize(subject, cb, null, options); + + assertEquals("Precondition", 0, subject.getPrincipals().size()); + assertTrue(module.login()); + assertTrue(module.commit()); + + assertEquals(4, subject.getPrincipals().size()); + + final List<String> roles = new ArrayList<String>(Arrays.asList("karaf", "test", "another")); + + boolean foundUser = false; + boolean foundRole = false; + for (Principal principal : subject.getPrincipals()) { + if (principal instanceof UserPrincipal) { + assertEquals("admin", principal.getName()); + foundUser = true; + } else if (principal instanceof RolePrincipal) { + assertTrue(roles.remove(principal.getName())); + foundRole = true; + } + } + assertTrue(foundUser); + assertTrue(foundRole); + assertTrue(roles.isEmpty()); + + assertTrue(module.logout()); + assertEquals("Principals should be gone as the user has logged out", 0, subject.getPrincipals().size()); + } + } \ No newline at end of file
