diff --git a/docs/security/shiroauthentication.md b/docs/security/shiroauthentication.md
index 969e61e5543..ea280bf8a3f 100644
--- a/docs/security/shiroauthentication.md
+++ b/docs/security/shiroauthentication.md
@@ -76,7 +76,7 @@ user3 = password4, role2
You can set the roles for each users next to the password.
## Groups and permissions (optional)
-In case you want to leverage user groups and permissions, use one of the following configuration for LDAP or AD under `[main]` segment in `shiro.ini`.
+In case you want to leverage user groups and permissions, use one of the following configuration for LDAPGroupRealm, LdapRealm from Knox support for AD Global Catalog Support and samAccountName or AD under `[main]` segment in `shiro.ini`.
```
activeDirectoryRealm = org.apache.zeppelin.server.ActiveDirectoryGroupRealm
@@ -87,6 +87,19 @@ activeDirectoryRealm.url = ldap://ldap.test.com:389
activeDirectoryRealm.groupRolesMap = "CN=aGroupName,OU=groups,DC=SOME_GROUP,DC=COMPANY,DC=COM":"group1"
activeDirectoryRealm.authorizationCachingEnabled = false
+ldapADGCRealm = org.apache.zeppelin.realm.LdapRealm
+ldapADGCRealm.contextFactory.systemUsername = CN=hdpbind,OU=Svc,DC=exadc,DC=w2k,DC=example,DC=com
+ldapADGCRealm.contextFactory.systemPassword = ldapPassword
+ldapADGCRealm.searchBase = dc=w2k,dc=example,dc=com
+ldapADGCRealm.userSearchBase = dc=w2k,dc=example,dc=com
+ldapADGCRealm.groupSearchBase = dc=w2k,dc=example,dc=com
+ldapADGCRealm.contextFactory.url = ldap://exampledc1.exadc.w2k.example.com:3268
+ldapADGCRealm.userSearchAttributeName = sAMAccountName
+ldapADGCRealm.contextFactory.authenticationMechanism = simple
+ldapADGCRealm.userObjectClass = user
+ldapADGCRealm.groupObjectClass = group
+ldapADGCRealm.memberAttribute = member
+
ldapRealm = org.apache.zeppelin.server.LdapGroupRealm
# search base for ldap groups (only relevant for LdapGroupRealm):
ldapRealm.contextFactory.environment[ldap.searchBase] = dc=COMPANY,dc=COM
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ActiveDirectoryGroupRealm.java b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ActiveDirectoryGroupRealm.java
similarity index 99%
rename from zeppelin-server/src/main/java/org/apache/zeppelin/server/ActiveDirectoryGroupRealm.java
rename to zeppelin-server/src/main/java/org/apache/zeppelin/realm/ActiveDirectoryGroupRealm.java
index 556f404663c..bf4e41d8784 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ActiveDirectoryGroupRealm.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/ActiveDirectoryGroupRealm.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.zeppelin.server;
+package org.apache.zeppelin.realm;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration;
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/LdapGroupRealm.java b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapGroupRealm.java
similarity index 98%
rename from zeppelin-server/src/main/java/org/apache/zeppelin/server/LdapGroupRealm.java
rename to zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapGroupRealm.java
index a718c77b816..fa0eccf8026 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/LdapGroupRealm.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapGroupRealm.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.zeppelin.server;
+package org.apache.zeppelin.realm;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
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
new file mode 100644
index 00000000000..301ef07111c
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/LdapRealm.java
@@ -0,0 +1,739 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.zeppelin.realm;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.crypto.hash.DefaultHashService;
+import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.crypto.hash.HashRequest;
+import org.apache.shiro.crypto.hash.HashService;
+import org.apache.shiro.realm.ldap.JndiLdapRealm;
+import org.apache.shiro.realm.ldap.LdapContextFactory;
+import org.apache.shiro.realm.ldap.LdapUtils;
+import org.apache.shiro.subject.MutablePrincipalCollection;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.naming.AuthenticationException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.LdapContext;
+import javax.naming.ldap.LdapName;
+
+
+/**
+ * Implementation of {@link org.apache.shiro.realm.ldap.JndiLdapRealm} that also
+ * returns each user's groups. This implementation is heavily based on
+ * org.apache.isis.security.shiro.IsisLdapRealm.
+ *
+ *
This implementation saves looked up ldap groups in Shiro Session to make them
+ * easy to be looked up outside of this object
+ *
+ *
Sample config for shiro.ini:
+ *
+ *
[main] ldapRealm=org.apache.zeppelin.server.LdapRealm
+ * ldapGroupContextFactory=org.apache.hadoop.gateway.shirorealm.
+ * LdapContextFactory ldapRealm.contextFactory=$ldapGroupContextFactory
+ * ldapRealm.contextFactory.authenticationMechanism=simple
+ * ldapRealm.contextFactory.url=ldap://localhost:33389
+ * ldapRealm.userDnTemplate=uid={0},ou=people,dc=hadoop,dc=apache,dc=org
+ * ldapRealm.authorizationEnabled=true
+ * ldapRealm.contextFactory.systemAuthenticationMechanism=simple
+ * ldapRealm.searchBase=ou=groups,dc=hadoop,dc=apache,dc=org
+ * ldapRealm.groupObjectClass=groupofnames ldapRealm.memberAttribute=member
+ * 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.clusterName=sandbox
+ * ldapRealm.contextFactory.systemPassword=S{ALIAS=ldcSystemPassword} [urls]
+ * **=authcBasic
+ *
+ *
# 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
+ *
+ *
ldapRealm.permissionsByRole=\ user_role = *:ToDoItemsJdo:*:*,\
+ * *:ToDoItem:*:*; \ self-install_role = *:ToDoItemsFixturesService:install:* ;
+ * \ admin_role = *
+ *
+ *
securityManager.realms = $ldapRealm
+ *
+ */
+public class LdapRealm extends JndiLdapRealm {
+
+ private static final SearchControls SUBTREE_SCOPE = new SearchControls();
+ private static final SearchControls ONELEVEL_SCOPE = new SearchControls();
+ private static final SearchControls OBJECT_SCOPE = new SearchControls();
+ private static final String SUBJECT_USER_ROLES = "subject.userRoles";
+ private static final String SUBJECT_USER_GROUPS = "subject.userGroups";
+ private static final String MEMBER_URL = "memberUrl";
+ private static final String POSIX_GROUP = "posixGroup";
+
+ private static Pattern TEMPLATE_PATTERN = Pattern.compile("\\{(\\d+?)\\}");
+ private static String DEFAULT_PRINCIPAL_REGEX = "(.*)";
+ private static final String MEMBER_SUBSTITUTION_TOKEN = "{0}";
+ private static final String HASHING_ALGORITHM = "SHA-1";
+ private static final Logger log = LoggerFactory.getLogger(LdapRealm.class);
+
+
+ static {
+ SUBTREE_SCOPE.setSearchScope(SearchControls.SUBTREE_SCOPE);
+ ONELEVEL_SCOPE.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+ OBJECT_SCOPE.setSearchScope(SearchControls.OBJECT_SCOPE);
+ }
+
+ private String searchBase;
+ private String userSearchBase;
+ private String principalRegex = DEFAULT_PRINCIPAL_REGEX;
+ private Pattern principalPattern = Pattern.compile(DEFAULT_PRINCIPAL_REGEX);
+ private String userDnTemplate = "{0}";
+ private String userSearchFilter = null;
+ private String userSearchAttributeTemplate = "{0}";
+ private String userSearchScope = "subtree";
+
+ private String groupSearchBase;
+
+ private String groupObjectClass = "groupOfNames";
+
+ // typical value: member, uniqueMember, meberUrl
+ private String memberAttribute = "member";
+
+ private String groupIdAttribute = "cn";
+
+ private String memberAttributeValuePrefix = "uid={0}";
+ private String memberAttributeValueSuffix = "";
+
+ private final Map rolesByGroup = new LinkedHashMap();
+ private final Map> permissionsByRole =
+ new LinkedHashMap>();
+
+ private boolean authorizationEnabled;
+
+ private String userSearchAttributeName;
+ private String userObjectClass = "person";
+
+ private HashService hashService = new DefaultHashService();
+
+ public LdapRealm() {
+ HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(HASHING_ALGORITHM);
+ setCredentialsMatcher(credentialsMatcher);
+ }
+
+ @Override
+
+ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
+ throws org.apache.shiro.authc.AuthenticationException {
+ try {
+ return super.doGetAuthenticationInfo(token);
+ } catch (org.apache.shiro.authc.AuthenticationException ae) {
+ throw ae;
+ }
+ }
+
+ /**
+ * Get groups from LDAP.
+ *
+ * @param principals
+ * the principals of the Subject whose AuthenticationInfo should
+ * be queried from the LDAP server.
+ * @param ldapContextFactory
+ * factory used to retrieve LDAP connections.
+ * @return an {@link AuthorizationInfo} instance containing information
+ * retrieved from the LDAP server.
+ * @throws NamingException
+ * if any LDAP errors occur during the search.
+ */
+ @Override
+ protected AuthorizationInfo queryForAuthorizationInfo(final PrincipalCollection principals,
+ final LdapContextFactory ldapContextFactory) throws NamingException {
+ if (!isAuthorizationEnabled()) {
+ return null;
+ }
+ final Set roleNames = getRoles(principals, ldapContextFactory);
+ log.info("Roles: " + roleNames);
+ SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roleNames);
+ Set stringPermissions = permsFor(roleNames);
+ simpleAuthorizationInfo.setStringPermissions(stringPermissions);
+ return simpleAuthorizationInfo;
+ }
+
+ private Set getRoles(PrincipalCollection principals,
+ final LdapContextFactory ldapContextFactory)
+ throws NamingException {
+ final String username = (String) getAvailablePrincipal(principals);
+
+ LdapContext systemLdapCtx = null;
+ try {
+ systemLdapCtx = ldapContextFactory.getSystemLdapContext();
+ return rolesFor(principals, username, systemLdapCtx, ldapContextFactory);
+ } catch (AuthenticationException ae) {
+ ae.printStackTrace();
+ return Collections.emptySet();
+ } finally {
+ LdapUtils.closeContext(systemLdapCtx);
+ }
+ }
+
+ private Set rolesFor(PrincipalCollection principals,
+ final String userName, final LdapContext ldapCtx,
+ final LdapContextFactory ldapContextFactory) throws NamingException {
+ final Set roleNames = new HashSet();
+ final Set groupNames = new HashSet();
+ NamingEnumeration searchResultEnum = null;
+ try {
+ // ldapsearch -h localhost -p 33389 -D
+ // uid=guest,ou=people,dc=hadoop,dc=apache,dc=org -w guest-password
+ // -b dc=hadoop,dc=apache,dc=org -s sub '(objectclass=*)'
+ searchResultEnum = ldapCtx.search(getGroupSearchBase(), "objectClass="
+ + groupObjectClass, SUBTREE_SCOPE);
+
+ String userDn = null;
+ if (userSearchAttributeName == null || userSearchAttributeName.isEmpty()) {
+ // memberAttributeValuePrefix and memberAttributeValueSuffix
+ // were computed from memberAttributeValueTemplate
+ userDn = memberAttributeValuePrefix + userName + memberAttributeValueSuffix;
+ } else {
+ userDn = getUserDn(userName);
+ }
+ while (searchResultEnum.hasMore()) { // searchResults contains all
+ // the groups in search
+ // scope
+ final SearchResult group = searchResultEnum.next();
+ addRoleIfMember(userDn, group, roleNames, groupNames, ldapContextFactory);
+ }
+
+ // save role names and group names in session so that they can be
+ // easily looked up outside of this object
+ SecurityUtils.getSubject().getSession().setAttribute(SUBJECT_USER_ROLES, roleNames);
+ SecurityUtils.getSubject().getSession().setAttribute(SUBJECT_USER_GROUPS, groupNames);
+ if (!groupNames.isEmpty() && (principals instanceof MutablePrincipalCollection)) {
+ ((MutablePrincipalCollection) principals).addAll(groupNames, getName());
+ }
+ log.info(roleNames + ", " + userName);
+ } finally {
+ if (searchResultEnum != null) {
+ searchResultEnum.close();
+ }
+ }
+ return roleNames;
+ }
+
+ private void addRoleIfMember(final String userDn, final SearchResult group,
+ final Set roleNames, final Set groupNames,
+ final LdapContextFactory ldapContextFactory) throws NamingException {
+
+ NamingEnumeration extends Attribute> attributeEnum = null;
+ NamingEnumeration> ne = null;
+ try {
+ LdapName userLdapDn = new LdapName(userDn);
+ Attribute attribute = group.getAttributes().get(getGroupIdAttribute());
+ String groupName = attribute.get().toString();
+
+ attributeEnum = group.getAttributes().getAll();
+ while (attributeEnum.hasMore()) {
+ final Attribute attr = attributeEnum.next();
+ if (!memberAttribute.equalsIgnoreCase(attr.getID())) {
+ continue;
+ }
+ ne = attr.getAll();
+ while (ne.hasMore()) {
+ String attrValue = ne.next().toString();
+ if (memberAttribute.equalsIgnoreCase(MEMBER_URL)) {
+ boolean dynamicGroupMember = isUserMemberOfDynamicGroup(userLdapDn, attrValue,
+ ldapContextFactory);
+ if (dynamicGroupMember) {
+ groupNames.add(groupName);
+ String roleName = roleNameFor(groupName);
+ if (roleName != null) {
+ roleNames.add(roleName);
+ } else {
+ roleNames.add(groupName);
+ }
+ }
+ } else {
+ if (groupObjectClass.equalsIgnoreCase(POSIX_GROUP)) {
+ attrValue = memberAttributeValuePrefix + attrValue + memberAttributeValueSuffix;
+ }
+ if (userLdapDn.equals(new LdapName(attrValue))) {
+ groupNames.add(groupName);
+ String roleName = roleNameFor(groupName);
+ if (roleName != null) {
+ roleNames.add(roleName);
+ } else {
+ roleNames.add(groupName);
+ }
+ break;
+ }
+ }
+ }
+ }
+ } finally {
+ try {
+ if (attributeEnum != null) {
+ attributeEnum.close();
+ }
+ } finally {
+ if (ne != null) {
+ ne.close();
+ }
+ }
+ }
+ }
+
+ public List getListRoles() {
+ List roleList = new ArrayList();
+ Map roles = getRolesByGroup();
+ Iterator it = roles.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry pair = (Map.Entry) it.next();
+ log.info(pair.getKey() + " = " + pair.getValue());
+ log.info("Key: " + pair.getKey());
+ log.info("Value: " + pair.getValue());
+ roleList.add((String) pair.getValue());
+ }
+ log.info("Permissions: " + getRolesByGroup());
+ return roleList;
+ }
+
+ private String roleNameFor(String groupName) {
+ return !rolesByGroup.isEmpty() ? rolesByGroup.get(groupName) : groupName;
+ }
+
+ private Set permsFor(Set roleNames) {
+ Set perms = new LinkedHashSet(); // preserve order
+ for (String role : roleNames) {
+ log.info("PermsForRole: " + role);
+ List permsForRole = permissionsByRole.get(role);
+ log.info("PermForByrole: " + permsForRole);
+ if (permsForRole != null) {
+ perms.addAll(permsForRole);
+ }
+ }
+ return perms;
+ }
+
+ public String getSearchBase() {
+ return searchBase;
+ }
+
+ public void setSearchBase(String searchBase) {
+ this.searchBase = searchBase;
+ }
+
+ public String getUserSearchBase() {
+ return (userSearchBase != null && !userSearchBase.isEmpty()) ? userSearchBase : searchBase;
+ }
+
+ public void setUserSearchBase(String userSearchBase) {
+ this.userSearchBase = userSearchBase;
+ }
+
+ public String getGroupSearchBase() {
+ return (groupSearchBase != null && !groupSearchBase.isEmpty()) ? groupSearchBase : searchBase;
+ }
+
+ public void setGroupSearchBase(String groupSearchBase) {
+ this.groupSearchBase = groupSearchBase;
+ }
+
+ public String getGroupObjectClass() {
+ return groupObjectClass;
+ }
+
+ public void setGroupObjectClass(String groupObjectClassAttribute) {
+ this.groupObjectClass = groupObjectClassAttribute;
+ }
+
+ public String getMemberAttribute() {
+ return memberAttribute;
+ }
+
+ public void setMemberAttribute(String memberAttribute) {
+ this.memberAttribute = memberAttribute;
+ }
+
+ public String getGroupIdAttribute() {
+ return groupIdAttribute;
+ }
+
+ public void setGroupIdAttribute(String groupIdAttribute) {
+ this.groupIdAttribute = groupIdAttribute;
+ }
+
+ /**
+ * Set Member Attribute Template for LDAP.
+ *
+ * @param template
+ * DN template to be used to query ldap.
+ * @throws IllegalArgumentException
+ * if template is empty or null.
+ */
+ public void setMemberAttributeValueTemplate(String template) {
+ if (!StringUtils.hasText(template)) {
+ String msg = "User DN template cannot be null or empty.";
+ throw new IllegalArgumentException(msg);
+ }
+ int index = template.indexOf(MEMBER_SUBSTITUTION_TOKEN);
+ if (index < 0) {
+ String msg = "Member attribute value template must contain the '" + MEMBER_SUBSTITUTION_TOKEN
+ + "' replacement token to understand how to " + "parse the group members.";
+ throw new IllegalArgumentException(msg);
+ }
+ String prefix = template.substring(0, index);
+ String suffix = template.substring(prefix.length() + MEMBER_SUBSTITUTION_TOKEN.length());
+ this.memberAttributeValuePrefix = prefix;
+ this.memberAttributeValueSuffix = suffix;
+ }
+
+ public void setRolesByGroup(Map rolesByGroup) {
+ this.rolesByGroup.putAll(rolesByGroup);
+ }
+
+ public Map getRolesByGroup() {
+ return rolesByGroup;
+ }
+
+ public void setPermissionsByRole(String permissionsByRoleStr) {
+ permissionsByRole.putAll(parsePermissionByRoleString(permissionsByRoleStr));
+ }
+
+ public Map> getPermissionsByRole() {
+ return permissionsByRole;
+ }
+
+ public boolean isAuthorizationEnabled() {
+ return authorizationEnabled;
+ }
+
+ public void setAuthorizationEnabled(boolean authorizationEnabled) {
+ this.authorizationEnabled = authorizationEnabled;
+ }
+
+ public String getUserSearchAttributeName() {
+ return userSearchAttributeName;
+ }
+
+ /**
+ * Set User Search Attribute Name for LDAP.
+ *
+ * @param userSearchAttributeName
+ * userAttribute to search ldap.
+ */
+ public void setUserSearchAttributeName(String userSearchAttributeName) {
+ if (userSearchAttributeName != null) {
+ userSearchAttributeName = userSearchAttributeName.trim();
+ }
+ this.userSearchAttributeName = userSearchAttributeName;
+ }
+
+ public String getUserObjectClass() {
+ return userObjectClass;
+ }
+
+ public void setUserObjectClass(String userObjectClass) {
+ this.userObjectClass = userObjectClass;
+ }
+
+ private Map> parsePermissionByRoleString(String permissionsByRoleStr) {
+ Map> perms = new HashMap>();
+
+ // split by semicolon ; then by eq = then by comma ,
+ StringTokenizer stSem = new StringTokenizer(permissionsByRoleStr, ";");
+ while (stSem.hasMoreTokens()) {
+ String roleAndPerm = stSem.nextToken();
+ StringTokenizer stEq = new StringTokenizer(roleAndPerm, "=");
+ if (stEq.countTokens() != 2) {
+ continue;
+ }
+ String role = stEq.nextToken().trim();
+ String perm = stEq.nextToken().trim();
+ StringTokenizer stCom = new StringTokenizer(perm, ",");
+ List permList = new ArrayList();
+ while (stCom.hasMoreTokens()) {
+ permList.add(stCom.nextToken().trim());
+ }
+ perms.put(role, permList);
+ }
+ return perms;
+ }
+
+ boolean isUserMemberOfDynamicGroup(LdapName userLdapDn, String memberUrl,
+ final LdapContextFactory ldapContextFactory) throws NamingException {
+
+ // ldap://host:port/dn?attributes?scope?filter?extensions
+
+ if (memberUrl == null) {
+ return false;
+ }
+ String[] tokens = memberUrl.split("\\?");
+ if (tokens.length < 4) {
+ return false;
+ }
+
+ String searchBaseString = tokens[0].substring(tokens[0].lastIndexOf("/") + 1);
+ String searchScope = tokens[2];
+ String searchFilter = tokens[3];
+
+ LdapName searchBaseDn = new LdapName(searchBaseString);
+
+ // do scope test
+ if (searchScope.equalsIgnoreCase("base")) {
+ return false;
+ }
+ if (!userLdapDn.toString().endsWith(searchBaseDn.toString())) {
+ return false;
+ }
+ if (searchScope.equalsIgnoreCase("one") && (userLdapDn.size() != searchBaseDn.size() - 1)) {
+ return false;
+ }
+ // search for the filter, substituting base with userDn
+ // search for base_dn=userDn, scope=base, filter=filter
+ LdapContext systemLdapCtx = null;
+ systemLdapCtx = ldapContextFactory.getSystemLdapContext();
+ boolean member = false;
+ NamingEnumeration searchResultEnum = null;
+ try {
+ searchResultEnum = systemLdapCtx.search(userLdapDn, searchFilter,
+ searchScope.equalsIgnoreCase("sub") ? SUBTREE_SCOPE : ONELEVEL_SCOPE);
+ if (searchResultEnum.hasMore()) {
+ return true;
+ }
+ } finally {
+ try {
+ if (searchResultEnum != null) {
+ searchResultEnum.close();
+ }
+ } finally {
+ LdapUtils.closeContext(systemLdapCtx);
+ }
+ }
+ return member;
+ }
+
+ public String getPrincipalRegex() {
+ return principalRegex;
+ }
+
+ /**
+ * Set Regex for Principal LDAP.
+ *
+ * @param regex
+ * regex to use to search for principal in shiro.
+ */
+ public void setPrincipalRegex(String regex) {
+ if (regex == null || regex.trim().isEmpty()) {
+ principalPattern = Pattern.compile(DEFAULT_PRINCIPAL_REGEX);
+ principalRegex = DEFAULT_PRINCIPAL_REGEX;
+ } else {
+ regex = regex.trim();
+ Pattern pattern = Pattern.compile(regex);
+ principalPattern = pattern;
+ principalRegex = regex;
+ }
+ }
+
+ public String getUserSearchAttributeTemplate() {
+ return userSearchAttributeTemplate;
+ }
+
+ public void setUserSearchAttributeTemplate(final String template) {
+ this.userSearchAttributeTemplate = (template == null ? null : template.trim());
+ }
+
+ public String getUserSearchFilter() {
+ return userSearchFilter;
+ }
+
+ public void setUserSearchFilter(final String filter) {
+ this.userSearchFilter = (filter == null ? null : filter.trim());
+ }
+
+ public String getUserSearchScope() {
+ return userSearchScope;
+ }
+
+ public void setUserSearchScope(final String scope) {
+ this.userSearchScope = (scope == null ? null : scope.trim().toLowerCase());
+ }
+
+ private SearchControls getUserSearchControls() {
+ SearchControls searchControls = SUBTREE_SCOPE;
+ if ("onelevel".equalsIgnoreCase(userSearchScope)) {
+ searchControls = ONELEVEL_SCOPE;
+ } else if ("object".equalsIgnoreCase(userSearchScope)) {
+ searchControls = OBJECT_SCOPE;
+ }
+ return searchControls;
+ }
+
+ @Override
+ public void setUserDnTemplate(final String template) throws IllegalArgumentException {
+ userDnTemplate = template;
+ }
+
+ private Matcher matchPrincipal(final String principal) {
+ Matcher matchedPrincipal = principalPattern.matcher(principal);
+ if (!matchedPrincipal.matches()) {
+ throw new IllegalArgumentException("Principal "
+ + principal + " does not match " + principalRegex);
+ }
+ return matchedPrincipal;
+ }
+
+ /**
+ * Returns the LDAP User Distinguished Name (DN) to use when acquiring an
+ * {@link javax.naming.ldap.LdapContext LdapContext} from the
+ * {@link LdapContextFactory}.
+ *
+ * If the the {@link #getUserDnTemplate() userDnTemplate} property has been
+ * set, this implementation will construct the User DN by substituting the
+ * specified {@code principal} into the configured template. If the
+ * {@link #getUserDnTemplate() userDnTemplate} has not been set, the method
+ * argument will be returned directly (indicating that the submitted
+ * authentication token principal is the User DN).
+ *
+ * @param principal
+ * the principal to substitute into the configured
+ * {@link #getUserDnTemplate() userDnTemplate}.
+ * @return the constructed User DN to use at runtime when acquiring an
+ * {@link javax.naming.ldap.LdapContext}.
+ * @throws IllegalArgumentException
+ * if the method argument is null or empty
+ * @throws IllegalStateException
+ * if the {@link #getUserDnTemplate userDnTemplate} has not been
+ * set.
+ * @see LdapContextFactory#getLdapContext(Object, Object)
+ */
+ @Override
+ protected String getUserDn(final String principal) throws IllegalArgumentException,
+ IllegalStateException {
+ String userDn;
+ Matcher matchedPrincipal = matchPrincipal(principal);
+ String userSearchBase = getUserSearchBase();
+ String userSearchAttributeName = getUserSearchAttributeName();
+
+ // If not searching use the userDnTemplate and return.
+ if ((userSearchBase == null || userSearchBase.isEmpty()) || (userSearchAttributeName == null
+ && userSearchFilter == null && !"object".equalsIgnoreCase(userSearchScope))) {
+ userDn = expandTemplate(userDnTemplate, matchedPrincipal);
+ log.info(userDn + "," + principal);
+ return userDn;
+ }
+
+ // Create the searchBase and searchFilter from config.
+ String searchBase = expandTemplate(getUserSearchBase(), matchedPrincipal);
+ String searchFilter = null;
+ if (userSearchFilter == null) {
+ if (userSearchAttributeName == null) {
+ searchFilter = String.format("(objectclass=%1$s)", getUserObjectClass());
+ } else {
+ searchFilter = String.format("(&(objectclass=%1$s)(%2$s=%3$s))", getUserObjectClass(),
+ userSearchAttributeName, expandTemplate(getUserSearchAttributeTemplate(),
+ matchedPrincipal));
+ }
+ } else {
+ searchFilter = expandTemplate(userSearchFilter, matchedPrincipal);
+ }
+ SearchControls searchControls = getUserSearchControls();
+
+ // Search for userDn and return.
+ LdapContext systemLdapCtx = null;
+ NamingEnumeration searchResultEnum = null;
+ try {
+ systemLdapCtx = getContextFactory().getSystemLdapContext();
+ log.info(searchBase + "," + searchFilter + "," + userSearchScope);
+ searchResultEnum = systemLdapCtx.search(searchBase, searchFilter, searchControls);
+ // SearchResults contains all the entries in search scope
+ if (searchResultEnum.hasMore()) {
+ SearchResult searchResult = searchResultEnum.next();
+ userDn = searchResult.getNameInNamespace();
+ log.info(userDn + "," + principal);
+ return userDn;
+ } else {
+ throw new IllegalArgumentException("Illegal principal name: " + principal);
+ }
+ } catch (AuthenticationException ne) {
+ ne.printStackTrace();
+ throw new IllegalArgumentException("Illegal principal name: " + principal);
+ } catch (NamingException ne) {
+ throw new IllegalArgumentException("Hit NamingException: " + ne.getMessage());
+ } finally {
+ try {
+ if (searchResultEnum != null) {
+ searchResultEnum.close();
+ }
+ } catch (NamingException ne) {
+ // Ignore exception on close.
+ } finally {
+ LdapUtils.closeContext(systemLdapCtx);
+ }
+ }
+ }
+
+ @Override
+ protected AuthenticationInfo createAuthenticationInfo(AuthenticationToken token,
+ Object ldapPrincipal,
+ Object ldapCredentials, LdapContext ldapContext) throws NamingException {
+ HashRequest.Builder builder = new HashRequest.Builder();
+ Hash credentialsHash = hashService
+ .computeHash(builder.setSource(token.getCredentials())
+ .setAlgorithmName(HASHING_ALGORITHM).build());
+ return new SimpleAuthenticationInfo(token.getPrincipal(),
+ credentialsHash.toHex(), credentialsHash.getSalt(),
+ getName());
+ }
+
+ private static final String expandTemplate(final String template, final Matcher input) {
+ String output = template;
+ Matcher matcher = TEMPLATE_PATTERN.matcher(output);
+ while (matcher.find()) {
+ String lookupStr = matcher.group(1);
+ int lookupIndex = Integer.parseInt(lookupStr);
+ String lookupValue = input.group(lookupIndex);
+ output = matcher.replaceFirst(lookupValue == null ? "" : lookupValue);
+ matcher = TEMPLATE_PATTERN.matcher(output);
+ }
+ return output;
+ }
+}
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java
index f1a895c8bcf..545dd31e451 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/GetUserList.java
@@ -24,7 +24,8 @@
import org.apache.shiro.realm.ldap.JndiLdapRealm;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.util.JdbcUtils;
-import org.apache.zeppelin.server.ActiveDirectoryGroupRealm;
+import org.apache.zeppelin.realm.ActiveDirectoryGroupRealm;
+import org.apache.zeppelin.realm.LdapRealm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -91,10 +92,15 @@ public List getRolesList(IniRealm r) {
*/
public List getUserList(JndiLdapRealm r, String searchText) {
List userList = new ArrayList<>();
+ LOG.info("SearchText: " + searchText);
String userDnTemplate = r.getUserDnTemplate();
+ LOG.info("Final userDnTemplate: " + userDnTemplate);
String userDn[] = userDnTemplate.split(",", 2);
+ LOG.info("Final userDn[]: " + userDn);
String userDnPrefix = userDn[0].split("=")[0];
+ LOG.info("Final userDnPrefix: " + userDnPrefix);
String userDnSuffix = userDn[1];
+ LOG.info("Final userDnSuffix: " + userDnSuffix);
JndiLdapContextFactory CF = (JndiLdapContextFactory) r.getContextFactory();
try {
LdapContext ctx = CF.getSystemLdapContext();
@@ -108,14 +114,58 @@ public List getUserList(JndiLdapRealm r, String searchText) {
Attributes attrs = ((SearchResult) result.next()).getAttributes();
if (attrs.get(userDnPrefix) != null) {
String currentUser = attrs.get(userDnPrefix).toString();
+ LOG.info("CurrentUser: " + currentUser);
userList.add(currentUser.split(":")[1].trim());
}
}
} catch (Exception e) {
LOG.error("Error retrieving User list from Ldap Realm", e);
}
+ LOG.info("UserList: " + userList);
return userList;
}
+
+ /**
+ * function to extract users from LDAP
+ */
+ public List getUserList(LdapRealm r, String searchText) {
+ List userList = new ArrayList<>();
+ LOG.info("SearchText: " + searchText);
+ String userAttribute = r.getUserSearchAttributeName();
+ String userSearchRealm = r.getUserSearchBase();
+ JndiLdapContextFactory CF = (JndiLdapContextFactory) r.getContextFactory();
+ try {
+ LdapContext ctx = CF.getSystemLdapContext();
+ SearchControls constraints = new SearchControls();
+ constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
+ String[] attrIDs = {userAttribute};
+ constraints.setReturningAttributes(attrIDs);
+ NamingEnumeration result = ctx.search(userSearchRealm, "("
+ + userAttribute + "=" + searchText + ")", constraints);
+ while (result.hasMore()) {
+ Attributes attrs = ((SearchResult) result.next()).getAttributes();
+ if (attrs.get(userAttribute) != null) {
+ String currentUser = (String) attrs.get(userAttribute).get();
+ LOG.info("CurrentUser: " + currentUser);
+ userList.add(currentUser.trim());
+ }
+ }
+ } catch (Exception e) {
+ LOG.error("Error retrieving User list from Ldap Realm", e);
+ }
+ LOG.info("UserList: " + userList);
+ return userList;
+ }
+
+ /***
+ * Get user roles from shiro.ini
+ * @param r
+ * @return
+ */
+ public List getRolesList(LdapRealm r) {
+ return r.getListRoles();
+ }
+
public List getUserList(ActiveDirectoryGroupRealm r, String searchText) {
List userList = new ArrayList<>();
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
index 727211292b2..70b920ebb56 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
@@ -197,8 +197,14 @@ public Response bind(@PathParam("noteId") String noteId) {
@Path("/")
@ZeppelinApi
public Response getNotebookList() throws IOException {
+ String principal = SecurityUtils.getPrincipal();
+ HashSet roles = SecurityUtils.getRoles();
+ HashSet userAndRoles = new HashSet<>();
+ userAndRoles.add(principal);
+ userAndRoles.addAll(roles);
AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
- List