org.apache.zeppelin.realm.LdapRealm Maven / Gradle / Ivy
/*
* 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 java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
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.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.PartialResultException;
import javax.naming.SizeLimitExceededException;
import javax.naming.directory.Attribute;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.PagedResultsControl;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.alias.CredentialProvider;
import org.apache.hadoop.security.alias.CredentialProviderFactory;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.ShiroException;
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.JndiLdapContextFactory;
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.session.Session;
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;
/**
* 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.realm.LdapRealm
* ldapRealm.contextFactory.url = ldap://localhost:33389
* ldapRealm.contextFactory.authenticationMechanism = simple
* ldapRealm.contextFactory.systemUsername = uid=guest,ou=people,dc=hadoop,dc= apache,dc=org
* ldapRealm.contextFactory.systemPassword = S{ALIAS=ldcSystemPassword}
* ldapRealm.hadoopSecurityCredentialPath = jceks://file/user/zeppelin/zeppelin.jceks
* 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.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.userObjectClass = person
* 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.userSearchFilter = (&(objectclass=person)(sAMAccountName={0}))
* ldapRealm.groupSearchFilter = (&(objectclass=groupofnames)(member={0}))
* ldapRealm.memberAttributeValueTemplate=cn={0},ou=people,dc=hadoop,dc=apache,dc=org
* # 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,\ DEMOS: self-install_role
*
* # optional list of roles that are allowed to authenticate
* ldapRealm.allowedRolesForAuthentication = admin_role,user_role
*
* ldapRealm.permissionsByRole=\ user_role = *:ToDoItemsJdo:*:*,\*:ToDoItem:*:*;
* \ self-install_role = *:ToDoItemsFixturesService:install:* ; \ admin_role = *
*
* [urls]
* **=authcBasic
*
* 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";
// LDAP Operator '1.2.840.113556.1.4.1941'
// walks the chain of ancestry in objects all the way to the root until it finds a match
// see https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx
private static final String MATCHING_RULE_IN_CHAIN_FORMAT =
"(&(objectClass=%s)(%s:1.2.840.113556.1.4.1941:=%s))";
private static final Pattern TEMPLATE_PATTERN = Pattern.compile("\\{(\\d+?)\\}");
private static final 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 int pagingSize = 100;
private boolean userLowerCase;
private String principalRegex = DEFAULT_PRINCIPAL_REGEX;
private Pattern principalPattern = Pattern.compile(DEFAULT_PRINCIPAL_REGEX);
private String userDnTemplate = "{0}";
private String userSearchFilter = null;
private String groupSearchFilter = null;
private String userSearchAttributeTemplate = "{0}";
private String userSearchScope = "subtree";
private String groupSearchScope = "subtree";
private boolean groupSearchEnableMatchingRuleInChain;
private String groupSearchBase;
private String groupObjectClass = "groupOfNames";
// typical value: member, uniqueMember, memberUrl
private String memberAttribute = "member";
private String groupIdAttribute = "cn";
private String memberAttributeValuePrefix = "uid=";
private String memberAttributeValueSuffix = "";
private final Map rolesByGroup = new LinkedHashMap<>();
private final List allowedRolesForAuthentication = new ArrayList<>();
private final Map> permissionsByRole = new LinkedHashMap<>();
private String hadoopSecurityCredentialPath;
final String keystorePass = "ldapRealm.systemPassword";
private boolean authorizationEnabled;
private String userSearchAttributeName;
private String userObjectClass = "person";
private HashService hashService = new DefaultHashService();
public void setHadoopSecurityCredentialPath(String hadoopSecurityCredentialPath) {
this.hadoopSecurityCredentialPath = hadoopSecurityCredentialPath;
}
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;
}
}
protected void onInit() {
super.onInit();
if (!org.apache.commons.lang3.StringUtils.isEmpty(this.hadoopSecurityCredentialPath)
&& getContextFactory() != null) {
((JndiLdapContextFactory) getContextFactory()).setSystemPassword(
getSystemPassword(this.hadoopSecurityCredentialPath, keystorePass));
}
}
static String getSystemPassword(String hadoopSecurityCredentialPath,
String keystorePass) {
String password = "";
try {
Configuration configuration = new Configuration();
configuration.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH,
hadoopSecurityCredentialPath);
CredentialProvider provider = CredentialProviderFactory.getProviders(configuration).get(0);
CredentialProvider.CredentialEntry credEntry = provider.getCredentialEntry(keystorePass);
if (credEntry != null) {
password = new String(credEntry.getCredential());
}
} catch (IOException e) {
throw new ShiroException("Error from getting credential entry from keystore", e);
}
if (org.apache.commons.lang3.StringUtils.isEmpty(password)) {
throw new ShiroException("Error getting SystemPassword from the provided keystore:"
+ keystorePass + ", in path:" + hadoopSecurityCredentialPath);
}
return password;
}
/**
* 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
* 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
public AuthorizationInfo queryForAuthorizationInfo(final PrincipalCollection principals,
final LdapContextFactory ldapContextFactory) throws NamingException {
if (!isAuthorizationEnabled()) {
return null;
}
final Set roleNames = getRoles(principals, ldapContextFactory);
if (log.isDebugEnabled()) {
log.debug("RolesNames Authorization: " + roleNames);
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roleNames);
Set stringPermissions = permsFor(roleNames);
simpleAuthorizationInfo.setStringPermissions(stringPermissions);
return simpleAuthorizationInfo;
}
private boolean hasAllowedAuthenticationRules(PrincipalCollection principals,
final LdapContextFactory ldapContextFactory) throws NamingException {
boolean allowed = allowedRolesForAuthentication.isEmpty();
if (!allowed) {
Set 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 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, SecurityUtils.getSubject().getSession());
} catch (Throwable t) {
log.warn("Failed to get roles in current context for " + username, t);
return Collections.emptySet();
} finally {
LdapUtils.closeContext(systemLdapCtx);
}
}
protected Set rolesFor(PrincipalCollection principals, String userNameIn,
final LdapContext ldapCtx, final LdapContextFactory ldapContextFactory, Session session)
throws NamingException {
final Set roleNames = new HashSet<>();
final Set groupNames = new HashSet<>();
final String userName;
if (getUserLowerCase()) {
log.debug("userLowerCase true");
userName = userNameIn.toLowerCase();
} else {
userName = userNameIn;
}
String userDn = getUserDnForSearch(userName);
// Activate paged results
int pageSize = getPagingSize();
if (log.isDebugEnabled()) {
log.debug("Ldap PagingSize: " + pageSize);
}
int numResults = 0;
byte[] cookie = null;
try {
ldapCtx.addToEnvironment(Context.REFERRAL, "ignore");
ldapCtx.setRequestControls(new Control[]{new PagedResultsControl(pageSize,
Control.NONCRITICAL)});
do {
// 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=*)'
NamingEnumeration searchResultEnum = null;
SearchControls searchControls = getGroupSearchControls();
try {
if (groupSearchEnableMatchingRuleInChain) {
searchResultEnum = ldapCtx.search(
getGroupSearchBase(),
String.format(
MATCHING_RULE_IN_CHAIN_FORMAT, groupObjectClass, memberAttribute, userDn),
searchControls);
while (searchResultEnum != null && searchResultEnum.hasMore()) {
// searchResults contains all the groups in search scope
numResults++;
final SearchResult group = searchResultEnum.next();
Attribute attribute = group.getAttributes().get(getGroupIdAttribute());
String groupName = attribute.get().toString();
String roleName = roleNameFor(groupName);
if (roleName != null) {
roleNames.add(roleName);
} else {
roleNames.add(groupName);
}
}
} else {
// Default group search filter
String searchFilter = String.format("(objectclass=%1$s)", groupObjectClass);
// If group search filter is defined in Shiro config, then use it
if (groupSearchFilter != null) {
searchFilter = expandTemplate(groupSearchFilter, userName);
//searchFilter = String.format("%1$s", groupSearchFilter);
}
if (log.isDebugEnabled()) {
log.debug("Group SearchBase|SearchFilter|GroupSearchScope: " + getGroupSearchBase()
+ "|" + searchFilter + "|" + groupSearchScope);
}
searchResultEnum = ldapCtx.search(
getGroupSearchBase(),
searchFilter,
searchControls);
while (searchResultEnum != null && searchResultEnum.hasMore()) {
// searchResults contains all the groups in search scope
numResults++;
final SearchResult group = searchResultEnum.next();
addRoleIfMember(userDn, group, roleNames, groupNames, ldapContextFactory);
}
}
} catch (PartialResultException e) {
log.debug("Ignoring PartitalResultException");
} finally {
if (searchResultEnum != null) {
searchResultEnum.close();
}
}
// Re-activate paged results
ldapCtx.setRequestControls(new Control[]{new PagedResultsControl(pageSize,
cookie, Control.CRITICAL)});
} while (cookie != null);
} catch (SizeLimitExceededException e) {
log.info("Only retrieved first " + numResults +
" groups due to SizeLimitExceededException.");
} catch (IOException e) {
log.error("Unabled to setup paged results");
}
// save role names and group names in session so that they can be
// easily looked up outside of this object
session.setAttribute(SUBJECT_USER_ROLES, roleNames);
session.setAttribute(SUBJECT_USER_GROUPS, groupNames);
if (!groupNames.isEmpty() && (principals instanceof MutablePrincipalCollection)) {
((MutablePrincipalCollection) principals).addAll(groupNames, getName());
}
if (log.isDebugEnabled()) {
log.debug("User RoleNames: " + userName + "::" + roleNames);
}
return roleNames;
}
protected String getUserDnForSearch(String userName) {
if (userSearchAttributeName == null || userSearchAttributeName.isEmpty()) {
// memberAttributeValuePrefix and memberAttributeValueSuffix
// were computed from memberAttributeValueTemplate
return memberDn(userName);
} else {
return getUserDn(userName);
}
}
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 {
// posix groups' members don' include the entire dn
if (groupObjectClass.equalsIgnoreCase(POSIX_GROUP)) {
attrValue = memberDn(attrValue);
}
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();
}
}
}
}
private String memberDn(String attrValue) {
return memberAttributeValuePrefix + attrValue + memberAttributeValueSuffix;
}
public Map getListRoles() {
Map groupToRoles = getRolesByGroup();
Map roles = new HashMap<>();
for (Map.Entry entry : groupToRoles.entrySet()) {
roles.put(entry.getValue(), entry.getKey());
}
return roles;
}
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) {
List permsForRole = permissionsByRole.get(role);
if (log.isDebugEnabled()) {
log.debug("PermsForRole: " + role);
log.debug("PermByRole: " + 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 int getPagingSize() {
return pagingSize;
}
public void setPagingSize(int pagingSize) {
this.pagingSize = pagingSize;
}
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 setAllowedRolesForAuthentication(List allowedRolesForAuthencation) {
this.allowedRolesForAuthentication.addAll(allowedRolesForAuthencation);
}
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 ("base".equalsIgnoreCase(searchScope)) {
log.debug("DynamicGroup SearchScope base");
return false;
}
if (!userLdapDn.toString().endsWith(searchBaseDn.toString())) {
return false;
}
if ("one".equalsIgnoreCase(searchScope) && (userLdapDn.size() != searchBaseDn.size() - 1)) {
log.debug("DynamicGroup SearchScope one");
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,
"sub".equalsIgnoreCase(searchScope) ? 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 getGroupSearchFilter() {
return groupSearchFilter;
}
public void setGroupSearchFilter(final String filter) {
this.groupSearchFilter = (filter == null ? null : filter.trim());
}
public boolean getUserLowerCase() {
return userLowerCase;
}
public void setUserLowerCase(boolean userLowerCase) {
this.userLowerCase = userLowerCase;
}
public String getUserSearchScope() {
return userSearchScope;
}
public void setUserSearchScope(final String scope) {
this.userSearchScope = (scope == null ? null : scope.trim().toLowerCase());
}
public String getGroupSearchScope() {
return groupSearchScope;
}
public void setGroupSearchScope(final String scope) {
this.groupSearchScope = (scope == null ? null : scope.trim().toLowerCase());
}
public boolean isGroupSearchEnableMatchingRuleInChain() {
return groupSearchEnableMatchingRuleInChain;
}
public void setGroupSearchEnableMatchingRuleInChain(
boolean groupSearchEnableMatchingRuleInChain) {
this.groupSearchEnableMatchingRuleInChain = groupSearchEnableMatchingRuleInChain;
}
private SearchControls getUserSearchControls() {
SearchControls searchControls = SUBTREE_SCOPE;
if ("onelevel".equalsIgnoreCase(userSearchScope)) {
searchControls = ONELEVEL_SCOPE;
} else if ("object".equalsIgnoreCase(userSearchScope)) {
searchControls = OBJECT_SCOPE;
}
return searchControls;
}
protected SearchControls getGroupSearchControls() {
SearchControls searchControls = SUBTREE_SCOPE;
if ("onelevel".equalsIgnoreCase(groupSearchScope)) {
searchControls = ONELEVEL_SCOPE;
} else if ("object".equalsIgnoreCase(groupSearchScope)) {
searchControls = OBJECT_SCOPE;
}
return searchControls;
}
@Override
public void setUserDnTemplate(final String template) throws IllegalArgumentException {
userDnTemplate = template;
}
private String matchPrincipal(final String principal) {
Matcher matchedPrincipal = principalPattern.matcher(principal);
if (!matchedPrincipal.matches()) {
throw new IllegalArgumentException("Principal "
+ principal + " does not match " + principalRegex);
}
return matchedPrincipal.group();
}
/**
* 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;
String 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);
if (log.isDebugEnabled()) {
log.debug("LDAP UserDN and Principal: " + 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();
if (log.isDebugEnabled()) {
log.debug("SearchBase,SearchFilter,UserSearchScope: " + 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();
if (log.isDebugEnabled()) {
log.debug("UserDN Returned,Principal: " + 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());
}
protected static final String expandTemplate(final String template, final String input) {
return template.replace(MEMBER_SUBSTITUTION_TOKEN, input);
}
}