From 335d8964636c311f179ada089ee78c532f15a59f Mon Sep 17 00:00:00 2001
From: Ancoron Luciferis <ancoron.luciferis@gmail.com>
Date: Mon, 24 Nov 2014 18:09:10 +0100
Subject: [PATCH] [jaas/modules] Add configuration parameter 'role.mapping' for
 LDAP integration

---
 .../karaf/jaas/modules/ldap/LDAPLoginModule.java   | 66 ++++++++++++++-
 .../jaas/modules/ldap/LdapLoginModuleTest.java     | 93 ++++++++++++++++++++++
 2 files changed, 158 insertions(+), 1 deletion(-)

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 846bcf8..65d5759 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,9 +32,11 @@ import javax.security.auth.callback.*;
 import javax.security.auth.login.LoginException;
 import java.io.IOException;
 import java.security.Principal;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.Map;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -57,6 +59,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 INITIAL_CONTEXT_FACTORY = "initial.context.factory";
     public static final String CONTEXT_PREFIX = "context.";
@@ -81,6 +84,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 String initialContextFactory = null;
     private boolean ssl;
@@ -104,6 +108,8 @@ 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));
+        String roleMappingOption = (String) options.get(ROLE_MAPPING);
+        roleMapping = parseRoleMapping(roleMappingOption);
         initialContextFactory = (String) options.get(INITIAL_CONTEXT_FACTORY);
         if (initialContextFactory == null) {
             initialContextFactory = DEFAULT_INITIAL_CONTEXT_FACTORY;
@@ -133,6 +139,32 @@ public class LDAPLoginModule extends AbstractKarafLoginModule {
         }
     }
 
+    private Map<String, Set<String>> parseRoleMapping(String option) {
+        Map<String, Set<String>> rm = new HashMap<String, Set<String>>();
+
+        if (option != null) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Parsing role mapping: " + option);
+            }
+
+            String[] mappings = option.split(";");
+            for (String mapping : mappings) {
+                String[] m = mapping.split("=", 2);
+                String remoteRole = m[0];
+                String[] karafRoles = m[1].split(",");
+                if (rm.get(remoteRole) == null) {
+                    rm.put(remoteRole, new HashSet<String>());
+                }
+                final Set<String> kr = rm.get(remoteRole);
+                for (String r : karafRoles) {
+                    kr.add(r);
+                }
+            }
+        }
+
+        return rm;
+    }
+
     public boolean login() throws LoginException {
         ClassLoader tccl = Thread.currentThread().getContextClassLoader();
         try {
@@ -307,7 +339,18 @@ public class LDAPLoginModule extends AbstractKarafLoginModule {
                     for (int i = 0; i < roles.size(); i++) {
                         String role = (String)roles.get(i);
                         if (role != null) {
-                            principals.add(new RolePrincipal(role));
+                            if (logger.isDebugEnabled()) {
+                                logger.debug("User '" + user + "' is a member of role '" + role + "'");
+                            }
+                            // handle role mapping...
+                            Set<RolePrincipal> mapped = tryMapRole(role);
+                            if (mapped.isEmpty()) {
+                                principals.add(new RolePrincipal(role));
+                            } else {
+                                for (RolePrincipal r : mapped) {
+                                    principals.add(r);
+                                }
+                            }
                         }
                     }
                 }
@@ -327,6 +370,27 @@ public class LDAPLoginModule extends AbstractKarafLoginModule {
         return true;
     }
 
+    protected Set<RolePrincipal> tryMapRole(String roleName) {
+        Set<RolePrincipal> roles = new HashSet<RolePrincipal>();
+
+        if (roleMapping == null || roleMapping.isEmpty()) {
+            return roles;
+        }
+
+        Set<String> karafRoles = roleMapping.get(roleName);
+        if (karafRoles != null) {
+            // add all mapped roles...
+            for (String karafRole : karafRoles) {
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Remote role '" + roleName + "' is mapped to karaf role '" + karafRole + "'");
+                }
+                roles.add(new RolePrincipal(karafRole));
+            }
+        }
+
+        return roles;
+    }
+
     protected void setupSsl(Hashtable env) throws LoginException {
         ServiceReference ref = null;
         try {
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 01135b0..11ced02 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
@@ -35,6 +35,10 @@ import javax.security.auth.callback.*;
 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;
@@ -181,5 +185,94 @@ public class LdapLoginModuleTest extends AbstractLdapTestUnit {
         assertEquals("Precondition", 0, subject.getPrincipals().size());
         assertFalse(module.login());
     }
+
+    @Test
+    public void testRoleMappingSimple() throws Exception {
+        Properties options = ldapLoginModuleOptions();
+        options.put(LDAPLoginModule.ROLE_MAPPING, "admin=karaf");
+        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(2, subject.getPrincipals().size());
+
+        boolean foundUser = false;
+        boolean foundRole = false;
+        for (Principal pr : subject.getPrincipals()) {
+            if (pr instanceof UserPrincipal) {
+                assertEquals("admin", pr.getName());
+                foundUser = true;
+            } else if (pr instanceof RolePrincipal) {
+                assertEquals("karaf", pr.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 pr : subject.getPrincipals()) {
+            if (pr instanceof UserPrincipal) {
+                assertEquals("admin", pr.getName());
+                foundUser = true;
+            } else if (pr instanceof RolePrincipal) {
+                assertTrue(roles.remove(pr.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
-- 
1.9.1

