Repository: zeppelin
Updated Branches:
  refs/heads/branch-0.7 e3071d66f -> 018afd7cb


Allow group/role based authentication using LdapRealm [ZEPPELIN-2539]

### What is this PR for?
Currently allowing authentication for selected roles/groups of an LDAP realm is 
not possible. The LDAPRealm allows for mapping of roles to groups but only 
allows authorization on URLs with respect to groups. No group based checks are 
carried out during authentication. This PR allows for group based 
authentication using LdapRealm.

### What type of PR is it?
[Improvement]

### Todos
* [x] - Merge #932 - This PR also merges changes from 932 so that needs to be 
merged first.

### What is the Jira issue?
https://issues.apache.org/jira/browse/ZEPPELIN-2539

### How should this be tested?
Build and configure `shiro.ini` to use the LdapRealm and verify that the realm 
works as before along with the added functionality of allowing only certain 
user groups for authentication if the `allowedRolesForAuthentication` config is 
set in the init. If this configuration is absent authentication should work as 
before without verifying roles. A sample shiro.ini is pasted here for testing 
purposes.

```
[main]
ldapRealm = org.apache.zeppelin.realm.LdapRealm
ldapRealm.userDnTemplate = uid={0},ou=people,dc=my-company,dc=net
ldapRealm.searchBase = dc=my-company,dc=net
ldapRealm.userSearchBase = ou=people,dc=my-company,dc=net
ldapRealm.groupSearchBase = ou=groups,dc=my-company,dc=net
ldapRealm.contextFactory.url = ldaps://auth.my-company.net:636
ldapRealm.contextFactory.authenticationMechanism = simple
ldapRealm.userObjectClass = posixAccount
ldapRealm.groupObjectClass = posixGroup
ldapRealm.authorizationEnabled = true
ldapRealm.memberAttribute = memberUid
ldapRealm.memberAttributeValueTemplate=uid={0},ou=people,dc=my-company,dc=net
ldapRealm.rolesByGroup = GLOBAL_ADMINS:admin,HKG_USERS:user
ldapRealm.allowedRolesForAuthentication = admin,user
ldapRealm.userSearchAttributeName = uid
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
shiro.loginUrl = /api/login
securityManager.sessionManager = $sessionManager
securityManager.sessionManager.globalSessionTimeout = 86400000
securityManager.realms = $ldapRealm

[urls]
/api/version = anon
/api/login = authc
/api/login/logout = authc
/** = authc, roles[admin,user]
```

### Screenshots (if appropriate)

### Questions:
* Does the licenses files need update? No
* Is there breaking changes for older versions? No
* Does this needs documentation? Y (documentation updated in PR)

Author: Sohaib Iftikhar <[email protected]>

Closes #2354 from sohaibiftikhar/ldaprealm and squashes the following commits:

f706b22 [Sohaib Iftikhar] Added role based authentication(not to be confused 
with authorization) for shiro

(cherry picked from commit 169fb742fd7f24ed987a7d85cf473e49f8d02f4b)
Signed-off-by: Lee moon soo <[email protected]>


Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/018afd7c
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/018afd7c
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/018afd7c

Branch: refs/heads/branch-0.7
Commit: 018afd7cb20375c4be227d7ebe9bd401147c9883
Parents: e3071d6
Author: Sohaib Iftikhar <[email protected]>
Authored: Fri May 19 15:04:18 2017 +0200
Committer: Lee moon soo <[email protected]>
Committed: Wed Jun 7 11:02:21 2017 -0700

----------------------------------------------------------------------
 docs/security/shiroauthentication.md            | 44 ++++++++++++++++++
 .../org/apache/zeppelin/realm/LdapRealm.java    | 48 ++++++++++++++++++++
 2 files changed, 92 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/018afd7c/docs/security/shiroauthentication.md
----------------------------------------------------------------------
diff --git a/docs/security/shiroauthentication.md 
b/docs/security/shiroauthentication.md
index 348c438..e608a14 100644
--- a/docs/security/shiroauthentication.md
+++ b/docs/security/shiroauthentication.md
@@ -128,6 +128,10 @@ Change the following values in the Shiro.ini file, and 
uncomment the line:
 
 ### LDAP
 
+Two options exist for configuring an LDAP Realm. The simpler to use is the 
LdapGroupRealm. How ever it has limited 
+flexibility with mapping of ldap groups to users and for authorization for 
user groups. A sample configuration file for
+this realm is given below.
+
 ```
 ldapRealm = org.apache.zeppelin.realm.LdapGroupRealm
 # search base for ldap groups (only relevant for LdapGroupRealm):
@@ -137,6 +141,46 @@ ldapRealm.userDnTemplate = 
uid={0},ou=Users,dc=COMPANY,dc=COM
 ldapRealm.contextFactory.authenticationMechanism = simple
 ```
 
+The other more flexible option is to use the LdapRealm. It allows for mapping 
of ldapgroups to roles and also allows for
+ role/group based authentication into the zeppelin server. Sample 
configuration for this realm is given below.
+ ```
+[main] 
+ldapRealm=org.apache.zeppelin.realm.LdapRealm
+
+ldapRealm.contextFactory.authenticationMechanism=simple
+ldapRealm.contextFactory.url=ldap://localhost:33389
+ldapRealm.userDnTemplate=uid={0},ou=people,dc=hadoop,dc=apache,dc=org
+# Ability to set ldap paging Size if needed default is 100
+ldapRealm.pagingSize = 200
+ldapRealm.authorizationEnabled=true
+ldapRealm.contextFactory.systemAuthenticationMechanism=simple
+ldapRealm.searchBase=dc=hadoop,dc=apache,dc=org
+ldapRealm.userSearchBase = dc=hadoop,dc=apache,dc=org
+ldapRealm.groupSearchBase = ou=groups,dc=hadoop,dc=apache,dc=org
+ldapRealm.groupObjectClass=groupofnames
+# Allow userSearchAttribute to be customized
+ldapRealm.userSearchAttributeName = sAMAccountName
+ldapRealm.memberAttribute=member
+# force usernames returned from ldap to lowercase useful for AD
+ldapRealm.userLowerCase = true 
+# ability set searchScopes subtree (default), one, base
+ldapRealm.userSearchScope = subtree;
+ldapRealm.groupSearchScope = subtree;
+ldapRealm.memberAttributeValueTemplate=cn={0},ou=people,dc=hadoop,dc=apache,dc=org
+ldapRealm.contextFactory.systemUsername=uid=guest,ou=people,dc=hadoop,dc=apache,dc=org
 
+ldapRealm.contextFactory.systemPassword=S{ALIAS=ldcSystemPassword}
+# enable support for nested groups using the LDAP_MATCHING_RULE_IN_CHAIN 
operator
+ldapRealm.groupSearchEnableMatchingRuleInChain = true
+# optional mapping from physical groups to logical application roles
+ldapRealm.rolesByGroup = LDN_USERS: user_role, NYK_USERS: user_role, 
HKG_USERS: user_role, GLOBAL_ADMIN: admin_role
+# optional list of roles that are allowed to authenticate. Incase not present 
all groups are allowed to authenticate (login). 
+# This changes nothing for url specific permissions that will continue to work 
as specified in [urls].
+ldapRealm.allowedRolesForAuthentication = admin_role,user_role
+ldapRealm.permissionsByRole= user_role = *:ToDoItemsJdo:*:*, *:ToDoItem:*:*; 
admin_role = *
+securityManager.sessionManager = $sessionManager
+securityManager.realms = $ldapRealm
+ ```
+
 ### PAM
 [PAM](https://en.wikipedia.org/wiki/Pluggable_authentication_module) 
authentication support allows the reuse of existing authentication 
 moduls on the host where Zeppelin is running. On a typical system modules are 
configured per service for example sshd, passwd, etc. under `/etc/pam.d/`. You 
can

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/018afd7c/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java
index 3381a51..97c223c 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java
@@ -109,6 +109,9 @@ import javax.naming.ldap.PagedResultsControl;
  * <p># optional mapping from physical groups to logical application roles
  * ldapRealm.rolesByGroup = \ LDN_USERS: user_role,\ NYK_USERS: user_role,\
  * HKG_USERS: user_role,\ GLOBAL_ADMIN: admin_role,\ DEMOS: self-install_role
+ *
+ * <p># optional list of roles that are allowed to authenticate
+ * ldapRealm.allowedRolesForAuthentication = admin_role,user_role
  * 
  * <p>ldapRealm.permissionsByRole=\ user_role = *:ToDoItemsJdo:*:*,\
  * *:ToDoItem:*:*; \ self-install_role = *:ToDoItemsFixturesService:install:* ;
@@ -176,6 +179,7 @@ public class LdapRealm extends JndiLdapRealm {
   private String memberAttributeValueSuffix = "";
 
   private final Map<String, String> rolesByGroup = new LinkedHashMap<String, 
String>();
+  private final List<String> allowedRolesForAuthentication = new 
ArrayList<String>();
   private final Map<String, List<String>> permissionsByRole = 
       new LinkedHashMap<String, List<String>>();
 
@@ -203,6 +207,29 @@ public class LdapRealm extends JndiLdapRealm {
   }
 
   /**
+   * This overrides the implementation of queryForAuthenticationInfo inside 
JndiLdapRealm.
+   * In addition to calling the super method for authentication it also tries 
to validate
+   * if this user has atleast one of the allowed roles for authentication. In 
case the property
+   * allowedRolesForAuthentication is empty this check always returns true.
+   *
+   * @param token the submitted authentication token that triggered the 
authentication attempt.
+   * @param ldapContextFactory factory used to retrieve LDAP connections.
+   * @return AuthenticationInfo instance representing the authenticated user's 
information.
+   * @throws NamingException if any LDAP errors occur.
+   */
+  @Override
+  protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken 
token,
+                                                          LdapContextFactory 
ldapContextFactory)
+      throws NamingException {
+    AuthenticationInfo info = super.queryForAuthenticationInfo(token, 
ldapContextFactory);
+    // Credentials were verified. Verify that the principal has all 
allowedRulesForAuthentication
+    if (!hasAllowedAuthenticationRules(info.getPrincipals(), 
ldapContextFactory)) {
+      throw new NamingException("Principal does not have any of the 
allowedRolesForAuthentication");
+    }
+    return info;
+  }
+
+  /**
   * Get groups from LDAP.
   * 
   * @param principals
@@ -231,6 +258,23 @@ public class LdapRealm extends JndiLdapRealm {
     return simpleAuthorizationInfo;
   }
 
+  private boolean hasAllowedAuthenticationRules(PrincipalCollection principals,
+                                                final LdapContextFactory 
ldapContextFactory)
+      throws NamingException {
+    boolean allowed = allowedRolesForAuthentication.isEmpty();
+    if (!allowed) {
+      Set<String> roles = getRoles(principals, ldapContextFactory);
+      for (String allowedRole: allowedRolesForAuthentication) {
+        if (roles.contains(allowedRole)) {
+          log.debug("Allowed role for user [" + allowedRole + "] found.");
+          allowed = true;
+          break;
+        }
+      }
+    }
+    return allowed;
+  }
+
   private Set<String> getRoles(PrincipalCollection principals, 
         final LdapContextFactory ldapContextFactory)
       throws NamingException {
@@ -524,6 +568,10 @@ public class LdapRealm extends JndiLdapRealm {
     this.memberAttributeValueSuffix = suffix;
   }
 
+  public void setAllowedRolesForAuthentication(List<String> 
allowedRolesForAuthencation) {
+    this.allowedRolesForAuthentication.addAll(allowedRolesForAuthencation);
+  }
+
   public void setRolesByGroup(Map<String, String> rolesByGroup) {
     this.rolesByGroup.putAll(rolesByGroup);
   }

Reply via email to