Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
372 lines (296 sloc) 14.1 KB
/*
* Copyright 2012 Red Hat, Inc. and/or its affiliates.
*
* Licensed 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.jbpm.services.task.identity;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.naming.Context;
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.InitialLdapContext;
import org.kie.api.task.UserGroupCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* LDAP integration for Task Service to collect user and role/group information.
*
* Following is a list of all supported properties:
* <ul>
* <li>ldap.bind.user (optional if LDAP server accepts anonymous access)</li>
* <li>ldap.bind.pwd (optional if LDAP server accepts anonymous access</li>
* <li>ldap.user.ctx (mandatory)</li>
* <li>ldap.role.ctx (mandatory)</li>
* <li>ldap.user.roles.ctx (optional, if not given ldap.role.ctx will be used)</li>
* <li>ldap.user.filter (mandatory)</li>
* <li>ldap.role.filter (mandatory)</li>
* <li>ldap.user.roles.filter (mandatory)</li>
* <li>ldap.user.attr.id (optional, if not given 'uid' will be used)</li>
* <li>ldap.roles.attr.id (optional, if not given 'cn' will be used)</li>
* <li>ldap.user.id.dn (optional, is user id a DN, instructs the callback to query for user DN before searching for roles, default false)</li>
* <li>ldap.search.scope (optional, if not given 'OBJECT_SCOPE' will be used) possible values are: OBJECT_SCOPE, ONELEVEL_SCOPE, SUBTREE_SCOPE</li>
* <li>ldap.name.escape (optional, instructs to escape - illegal character in user/group name before the query - currently escapes only comma) by default is set to true</li>
* <li>java.naming.factory.initial</li>
* <li>java.naming.security.authentication</li>
* <li>java.naming.security.protocol</li>
* <li>java.naming.provider.url</li>
* <li></li>
* </ul>
*/
public class LDAPUserGroupCallbackImpl extends AbstractUserGroupInfo implements UserGroupCallback {
private static final Logger logger = LoggerFactory.getLogger(LDAPUserGroupCallbackImpl.class);
protected static final String DEFAULT_PROPERTIES_NAME = "classpath:/jbpm.usergroup.callback.properties";
public static final String BIND_USER = "ldap.bind.user";
public static final String BIND_PWD = "ldap.bind.pwd";
public static final String USER_CTX = "ldap.user.ctx";
public static final String ROLE_CTX = "ldap.role.ctx";
public static final String USER_ROLES_CTX = "ldap.user.roles.ctx";
public static final String USER_FILTER = "ldap.user.filter";
public static final String ROLE_FILTER = "ldap.role.filter";
public static final String USER_ROLES_FILTER = "ldap.user.roles.filter";
public static final String USER_ATTR_ID = "ldap.user.attr.id";
public static final String ROLE_ATTR_ID = "ldap.roles.attr.id";
public static final String IS_USER_ID_DN = "ldap.user.id.dn";
public static final String SEARCH_SCOPE = "ldap.search.scope";
public static final String LDAP_NAME_ESCAPE = "ldap.name.escape";
protected static final String[] requiredProperties = {USER_CTX, ROLE_CTX, USER_FILTER, ROLE_FILTER, USER_ROLES_FILTER};
private Properties config;
//no no-arg constructor to prevent cdi from auto deploy
public LDAPUserGroupCallbackImpl(boolean activate) {
String propertiesLocation = System.getProperty("jbpm.usergroup.callback.properties");
config = readProperties(propertiesLocation, DEFAULT_PROPERTIES_NAME);
validate();
}
public LDAPUserGroupCallbackImpl(Properties config) {
this.config = config;
validate();
}
public boolean existsUser(String userId) {
userId = escapeIllegalChars(userId);
InitialLdapContext ctx = null;
boolean exists = false;
try {
ctx = buildInitialLdapContext();
String userContext = this.config.getProperty(USER_CTX);
String userFilter = this.config.getProperty(USER_FILTER);
String userAttrId = this.config.getProperty(USER_ATTR_ID, "uid");
userFilter = userFilter.replaceAll("\\{0\\}", userId);
logger.debug("Seaching for user existence with filter {} on context {}", userFilter, userContext);
SearchControls constraints = new SearchControls();
String searchScope = this.config.getProperty(SEARCH_SCOPE);
if (searchScope != null) {
constraints.setSearchScope(parseSearchScope(searchScope));
}
NamingEnumeration<SearchResult> result = ctx.search(userContext, userFilter, constraints);
if (result.hasMore()) {
SearchResult sr = result.next();
Attribute ldapUserId = sr.getAttributes().get(userAttrId);
if (ldapUserId.contains(userId)) {
exists = true;
}
logger.debug("Entry in LDAP found and result of matching with given user id is {}", exists);
}
result.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
}
}
}
return exists;
}
public boolean existsGroup(String groupId) {
groupId = escapeIllegalChars(groupId);
InitialLdapContext ctx = null;
boolean exists = false;
try {
ctx = buildInitialLdapContext();
String roleContext = this.config.getProperty(ROLE_CTX);
String roleFilter = this.config.getProperty(ROLE_FILTER);
String roleAttrId = this.config.getProperty(ROLE_ATTR_ID, "cn");
roleFilter = roleFilter.replaceAll("\\{0\\}", groupId);
SearchControls constraints = new SearchControls();
String searchScope = this.config.getProperty(SEARCH_SCOPE);
if (searchScope != null) {
constraints.setSearchScope(parseSearchScope(searchScope));
}
NamingEnumeration<SearchResult> result = ctx.search(roleContext, roleFilter, constraints);
if (result.hasMore()) {
SearchResult sr = result.next();
Attribute ldapUserId = sr.getAttributes().get(roleAttrId);
if (ldapUserId.contains(groupId)) {
exists = true;
}
}
result.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
}
}
}
return exists;
}
public List<String> getGroupsForUser(String userId, List<String> groupIds, List<String> allExistingGroupIds) {
InitialLdapContext ctx = null;
List<String> userGroups = new ArrayList<String>();
try {
ctx = buildInitialLdapContext();
String userDN = userId;
// if user id is not DN look it up first in ldap
if (!Boolean.parseBoolean(this.config.getProperty(IS_USER_ID_DN, "false"))) {
logger.debug("User id is not DN, looking up user first...");
String userContext = this.config.getProperty(USER_CTX);
String userFilter = this.config.getProperty(USER_FILTER);
SearchControls constraints = new SearchControls();
String searchScope = this.config.getProperty(SEARCH_SCOPE);
if (searchScope != null) {
constraints.setSearchScope(parseSearchScope(searchScope));
}
logger.debug("Searching for user DN with filter {} on context {}", userFilter, userContext);
NamingEnumeration<SearchResult> result = ctx.search(userContext, userFilter, new Object[]{userId}, constraints);
if (result.hasMore()) {
SearchResult searchResult = result.nextElement();
userDN = searchResult.getNameInNamespace();
logger.debug("User DN found, DN is {}", userDN);
}
result.close();
}
String roleContext = this.config.getProperty(USER_ROLES_CTX, this.config.getProperty(ROLE_CTX));
String roleFilter = this.config.getProperty(USER_ROLES_FILTER);
String roleAttrId = this.config.getProperty(ROLE_ATTR_ID, "cn");
SearchControls constraints = new SearchControls();
String searchScope = this.config.getProperty(SEARCH_SCOPE);
if (searchScope != null) {
constraints.setSearchScope(parseSearchScope(searchScope));
}
logger.debug("Searching for groups for user with filter {} on context {}", roleFilter, roleContext);
NamingEnumeration<SearchResult> result = ctx.search(roleContext, roleFilter, new Object[]{userDN}, constraints);
if (result.hasMore()) {
SearchResult searchResult = null;
String name = null;
while (result.hasMore()) {
searchResult = result.nextElement();
name = unescapeIllegalChars((String) searchResult.getAttributes().get(roleAttrId).get());
logger.debug("Found group {}", name);
userGroups.add(name);
}
}
result.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
}
}
}
return userGroups;
}
protected void validate() {
if (this.config == null) {
throw new IllegalArgumentException("No configuration found for LDAPUserGroupCallbackImpl, aborting...");
}
StringBuffer missingRequiredProps = new StringBuffer();
for (String requiredProp : requiredProperties) {
if (!this.config.containsKey(requiredProp)) {
if (missingRequiredProps.length() > 0) {
missingRequiredProps.append(", ");
}
missingRequiredProps.append(requiredProp);
}
}
if (missingRequiredProps.length() > 0) {
logger.debug("Validation failed due to missing required properties: {}", missingRequiredProps.toString());
throw new IllegalArgumentException("Missing required properties to configure LDAPUserGroupCallbackImpl: " + missingRequiredProps.toString());
}
}
protected InitialLdapContext buildInitialLdapContext() throws NamingException {
// Set defaults for key values if they are missing
String factoryName = this.config.getProperty(Context.INITIAL_CONTEXT_FACTORY);
if (factoryName == null) {
factoryName = "com.sun.jndi.ldap.LdapCtxFactory";
this.config.setProperty(Context.INITIAL_CONTEXT_FACTORY, factoryName);
}
String authType = this.config.getProperty(Context.SECURITY_AUTHENTICATION);
if (authType == null) {
this.config.setProperty(Context.SECURITY_AUTHENTICATION, "simple");
}
String protocol = this.config.getProperty(Context.SECURITY_PROTOCOL);
String providerURL = (String) this.config.getProperty(Context.PROVIDER_URL);
if (providerURL == null) {
providerURL = "ldap://localhost:" + ((protocol != null && protocol.equals("ssl")) ? "636" : "389");
this.config.setProperty(Context.PROVIDER_URL, providerURL);
}
String binduser = this.config.getProperty(BIND_USER);
if (binduser != null) {
this.config.setProperty(Context.SECURITY_PRINCIPAL, binduser);
}
String bindpwd = this.config.getProperty(BIND_PWD);
if (binduser != null) {
this.config.setProperty(Context.SECURITY_CREDENTIALS, bindpwd);
}
if (logger.isDebugEnabled()) {
logger.debug("Using following InitialLdapContext properties:");
logger.debug("Factory {}", this.config.getProperty(Context.INITIAL_CONTEXT_FACTORY));
logger.debug("Authentication {}", this.config.getProperty(Context.SECURITY_AUTHENTICATION));
logger.debug("Protocol {}", this.config.getProperty(Context.SECURITY_PROTOCOL));
logger.debug("Provider URL {}", this.config.getProperty(Context.PROVIDER_URL));
}
return new InitialLdapContext(this.config, null);
}
protected int parseSearchScope(String searchScope) {
logger.debug("Search scope: {}", searchScope);
if ("OBJECT_SCOPE".equals(searchScope))
return 0;
else if ("ONELEVEL_SCOPE".equals(searchScope))
return 1;
else if ("SUBTREE_SCOPE".equals(searchScope))
return 2;
// Default set to ONELEVEL_SCOPE
return 1;
}
private boolean escapeOn() {
return Boolean.parseBoolean(this.config.getProperty(LDAP_NAME_ESCAPE, "true"));
}
protected String escapeIllegalChars(String entityId) {
if (!escapeOn()) {
return entityId;
}
return entityId.replace(",", "\\,");
}
protected String unescapeIllegalChars(String entityId) {
if (!escapeOn()) {
return entityId;
}
return entityId.replace("\\,", ",");
}
}