diff --git a/engine-plugins/identity-ldap/src/main/java/org/camunda/bpm/identity/impl/ldap/LdapClient.java b/engine-plugins/identity-ldap/src/main/java/org/camunda/bpm/identity/impl/ldap/LdapClient.java new file mode 100644 index 00000000000..f9a6c090bad --- /dev/null +++ b/engine-plugins/identity-ldap/src/main/java/org/camunda/bpm/identity/impl/ldap/LdapClient.java @@ -0,0 +1,184 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; 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.camunda.bpm.identity.impl.ldap; + +import org.camunda.bpm.engine.impl.identity.IdentityProviderException; +import org.camunda.bpm.identity.impl.ldap.util.LdapPluginLogger; + +import javax.naming.AuthenticationException; +import javax.naming.Context; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.SearchResult; +import javax.naming.ldap.Control; +import javax.naming.ldap.InitialLdapContext; +import javax.naming.ldap.LdapContext; +import javax.naming.ldap.PagedResultsControl; +import javax.naming.ldap.SortControl; +import javax.naming.ldap.SortKey; +import java.io.IOException; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +/** + * This wrapper class should ensure that LDAP exceptions are wrapped as process engine exceptions + * to avoid that error details are disclosed in the REST API. + */ +public class LdapClient { + + protected LdapContext initialContext; + protected LdapConfiguration ldapConfiguration; + + public LdapClient(LdapConfiguration ldapConfiguration) { + this.ldapConfiguration = ldapConfiguration; + } + + protected void ensureContextInitialized() { + if (initialContext == null) { + initialContext = openContext(); + } + } + + public LdapContext openContext(String dn, String password) { + Hashtable env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, ldapConfiguration.getInitialContextFactory()); + env.put(Context.SECURITY_AUTHENTICATION, ldapConfiguration.getSecurityAuthentication()); + env.put(Context.PROVIDER_URL, ldapConfiguration.getServerUrl()); + env.put(Context.SECURITY_PRINCIPAL, dn); + env.put(Context.SECURITY_CREDENTIALS, password); + + // for anonymous login + if (ldapConfiguration.isAllowAnonymousLogin() && password.isEmpty()) { + env.put(Context.SECURITY_AUTHENTICATION, "none"); + } + + if (ldapConfiguration.isUseSsl()) { + env.put(Context.SECURITY_PROTOCOL, "ssl"); + } + + // add additional properties + Map contextProperties = ldapConfiguration.getContextProperties(); + if (contextProperties != null) { + env.putAll(contextProperties); + } + + try { + return new InitialLdapContext(env, null); + + } catch (AuthenticationException e) { + throw new LdapAuthenticationException("Could not authenticate with LDAP server", e); + + } catch (NamingException e) { + throw new IdentityProviderException("Could not connect to LDAP server", e); + + } + } + + protected LdapContext openContext() { + return openContext(ldapConfiguration.getManagerDn(), ldapConfiguration.getManagerPassword()); + } + + protected void closeLdapCtx() { + closeLdapCtx(initialContext); + } + + protected void closeLdapCtx(LdapContext context) { + if (context != null) { + try { + context.close(); + } catch (NamingException e) { + // ignore + LdapPluginLogger.INSTANCE.exceptionWhenClosingLdapContext(e); + } + } + } + + public LdapSearchResults search(String baseDn, String searchFilter) { + try { + return new LdapSearchResults(initialContext.search(baseDn, searchFilter, ldapConfiguration.getSearchControls())); + } catch (NamingException e) { + throw new IdentityProviderException("LDAP search request failed.", e); + } + } + + public void setRequestControls(List listControls) { + try { + initialContext.setRequestControls(listControls.toArray(new Control[0])); + } catch (NamingException e) { + throw new IdentityProviderException("LDAP server failed to set request controls.", e); + } + } + + public Control[] getResponseControls() { + try { + return initialContext.getResponseControls(); + } catch (NamingException e) { + throw new IdentityProviderException("Error occurred while getting the response controls from the LDAP server.", e); + } + } + + public static void addPaginationControl(List listControls, byte[] cookie, Integer pageSize) { + try { + listControls.add(new PagedResultsControl(pageSize, cookie, Control.NONCRITICAL)); + } catch (IOException e) { + throw new IdentityProviderException("Pagination couldn't be enabled.", e); + } + } + + public static void addSortKey(SortKey sortKey, List controls) { + try { + controls.add(new SortControl(new SortKey[] { sortKey }, Control.CRITICAL)); + } catch (IOException e) { + throw new IdentityProviderException("Sorting couldn't be enabled.", e); + } + } + + protected static String getValue(String attrName, Attributes attributes) { + Attribute attribute = attributes.get(attrName); + if (attribute != null) { + try { + return (String) attribute.get(); + } catch (NamingException e) { + throw new IdentityProviderException("Error occurred while retrieving the value.", e); + } + } else { + return null; + } + } + + @SuppressWarnings("unchecked") + public static NamingEnumeration getAllMembers(String attributeId, LdapSearchResults searchResults) { + SearchResult result = searchResults.nextElement(); + Attributes attributes = result.getAttributes(); + if (attributes != null) { + Attribute memberAttribute = attributes.get(attributeId); + if (memberAttribute != null) { + try { + return (NamingEnumeration) memberAttribute.getAll(); + } catch (NamingException e) { + throw new IdentityProviderException("Value couldn't be retrieved.", e); + } + } + } + + return null; + } + +} diff --git a/engine-plugins/identity-ldap/src/main/java/org/camunda/bpm/identity/impl/ldap/LdapIdentityProviderSession.java b/engine-plugins/identity-ldap/src/main/java/org/camunda/bpm/identity/impl/ldap/LdapIdentityProviderSession.java index 9a365c964ed..8682471fcab 100644 --- a/engine-plugins/identity-ldap/src/main/java/org/camunda/bpm/identity/impl/ldap/LdapIdentityProviderSession.java +++ b/engine-plugins/identity-ldap/src/main/java/org/camunda/bpm/identity/impl/ldap/LdapIdentityProviderSession.java @@ -24,28 +24,22 @@ import static org.camunda.bpm.identity.impl.ldap.LdapConfiguration.DB_QUERY_WILDCARD; import static org.camunda.bpm.identity.impl.ldap.LdapConfiguration.LDAP_QUERY_WILDCARD; -import java.io.IOException; import java.io.StringWriter; + import java.util.ArrayList; -import java.util.Hashtable; import java.util.List; -import java.util.Map; -import javax.naming.AuthenticationException; -import javax.naming.Context; +import java.util.function.Function; +import java.util.function.Predicate; + import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.SearchResult; import javax.naming.ldap.Control; -import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; -import javax.naming.ldap.PagedResultsControl; -import javax.naming.ldap.PagedResultsResponseControl; -import javax.naming.ldap.SortControl; import javax.naming.ldap.SortKey; +import javax.naming.ldap.PagedResultsResponseControl; + import org.camunda.bpm.engine.BadUserRequestException; -import org.camunda.bpm.engine.authorization.Permission; import org.camunda.bpm.engine.authorization.Resource; import org.camunda.bpm.engine.identity.Group; import org.camunda.bpm.engine.identity.GroupQuery; @@ -55,16 +49,17 @@ import org.camunda.bpm.engine.identity.User; import org.camunda.bpm.engine.identity.UserQuery; import org.camunda.bpm.engine.impl.AbstractQuery; -import org.camunda.bpm.engine.impl.Direction; -import org.camunda.bpm.engine.impl.GroupQueryProperty; import org.camunda.bpm.engine.impl.QueryOrderingProperty; import org.camunda.bpm.engine.impl.UserQueryImpl; import org.camunda.bpm.engine.impl.UserQueryProperty; +import org.camunda.bpm.engine.impl.GroupQueryProperty; +import org.camunda.bpm.engine.impl.db.DbEntity; import org.camunda.bpm.engine.impl.identity.IdentityProviderException; import org.camunda.bpm.engine.impl.identity.ReadOnlyIdentityProvider; import org.camunda.bpm.engine.impl.interceptor.CommandContext; import org.camunda.bpm.engine.impl.persistence.entity.GroupEntity; import org.camunda.bpm.engine.impl.persistence.entity.UserEntity; +import org.camunda.bpm.engine.impl.Direction; import org.camunda.bpm.identity.impl.ldap.util.LdapPluginLogger; /** @@ -75,11 +70,12 @@ public class LdapIdentityProviderSession implements ReadOnlyIdentityProvider { protected LdapConfiguration ldapConfiguration; - // one object of this class is created per thread. One initialContext is created per thread. - protected LdapContext initialContext; + + protected LdapClient ldapClient; public LdapIdentityProviderSession(LdapConfiguration ldapConfiguration) { this.ldapConfiguration = ldapConfiguration; + this.ldapClient = new LdapClient(ldapConfiguration); } // Session Lifecycle ////////////////////////////////// @@ -89,67 +85,13 @@ public void flush() { } public void close() { - closeLdapCtx(initialContext); - } - - protected void closeLdapCtx(LdapContext context) { - if (context != null) { - try { - context.close(); - } catch (Exception e) { - // ignore - LdapPluginLogger.INSTANCE.exceptionWhenClosingLdapCOntext(e); - } - } - } - - protected InitialLdapContext openContext(String userDn, String password) { - Hashtable env = new Hashtable<>(); - env.put(Context.INITIAL_CONTEXT_FACTORY, ldapConfiguration.getInitialContextFactory()); - env.put(Context.SECURITY_AUTHENTICATION, ldapConfiguration.getSecurityAuthentication()); - env.put(Context.PROVIDER_URL, ldapConfiguration.getServerUrl()); - env.put(Context.SECURITY_PRINCIPAL, userDn); - env.put(Context.SECURITY_CREDENTIALS, password); - - // for anonymous login - if (ldapConfiguration.isAllowAnonymousLogin() && password.isEmpty()) { - env.put(Context.SECURITY_AUTHENTICATION, "none"); - } - - if (ldapConfiguration.isUseSsl()) { - env.put(Context.SECURITY_PROTOCOL, "ssl"); - } - - // add additional properties - Map contextProperties = ldapConfiguration.getContextProperties(); - if (contextProperties != null) { - env.putAll(contextProperties); - } - - try { - return new InitialLdapContext(env, null); - - } catch (AuthenticationException e) { - throw new LdapAuthenticationException("Could not authenticate with LDAP server", e); - - } catch (NamingException e) { - throw new IdentityProviderException("Could not connect to LDAP server", e); - - } - } - - protected void ensureContextInitialized() { - if (initialContext == null) { - initialContext = openContext(ldapConfiguration.getManagerDn(), ldapConfiguration.getManagerPassword()); - } + ldapClient.closeLdapCtx(); } // Users ///////////////////////////////////////////////// public User findUserById(String userId) { - return createUserQuery(getCommandContext()) - .userId(userId) - .singleResult(); + return createUserQuery(getCommandContext()).userId(userId).singleResult(); } public UserQuery createUserQuery() { @@ -166,12 +108,12 @@ public NativeUserQuery createNativeUserQuery() { } public long findUserCountByQueryCriteria(LdapUserQueryImpl query) { - ensureContextInitialized(); + ldapClient.ensureContextInitialized(); return findUserByQueryCriteria(query).size(); } public List findUserByQueryCriteria(LdapUserQueryImpl query) { - ensureContextInitialized(); + ldapClient.ensureContextInitialized(); // convert DB wildcards to LDAP wildcards if necessary if (query.getEmailLike() != null) { @@ -193,197 +135,59 @@ public List findUserByQueryCriteria(LdapUserQueryImpl query) { } } - protected List findUsersByGroupId(LdapUserQueryImpl query) { - StringBuilder resultLogger = new StringBuilder(); - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("findUsersByGroupId: from "); - resultLogger.append(query.getFirstResult()); - } + protected boolean paginationContinues(int currentSize, int maxResults) { + return nextPageDetected() && currentSize < maxResults; + } + protected List findUsersByGroupId(LdapUserQueryImpl query) { String baseDn = getDnForGroup(query.getGroupId()); // compose group search filter String groupSearchFilter = "(& " + ldapConfiguration.getGroupSearchFilter() + ")"; - NamingEnumeration enumeration = null; - try { - initializeControls(query, resultLogger); - - List groupMemberList = new ArrayList<>(); - int resultCount = 0; - int pageNumber = 0; - - do { - enumeration = initialContext.search(baseDn, groupSearchFilter, ldapConfiguration.getSearchControls()); - pageNumber++; - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append(", (page:"); - resultLogger.append(pageNumber); - resultLogger.append(")"); - } + initializeControls(query); - // first find group - while (enumeration.hasMoreElements()) { - SearchResult result = enumeration.nextElement(); - Attribute memberAttribute = result.getAttributes().get(ldapConfiguration.getGroupMemberAttribute()); - if (null != memberAttribute) { - NamingEnumeration allMembers = memberAttribute.getAll(); + List groupMembers = new ArrayList<>(); + int resultCount = 0; + do { + try (LdapSearchResults searchResults = ldapClient.search(baseDn, groupSearchFilter)) { + // first find group + while (searchResults.hasMoreElements()) { + String groupMemberAttribute = ldapConfiguration.getGroupMemberAttribute(); + NamingEnumeration allGroupMembers = LdapClient.getAllMembers(groupMemberAttribute, searchResults); + if (allGroupMembers != null) { // iterate group members - while (allMembers.hasMoreElements()) { + while (allGroupMembers.hasMoreElements()) { if (resultCount >= query.getFirstResult()) { - groupMemberList.add((String) allMembers.nextElement()); + groupMembers.add(allGroupMembers.nextElement()); } resultCount++; } } } - } while (isNextPageDetected(resultLogger) && groupMemberList.size() < query.getMaxResults()); - - List userList = new ArrayList<>(); - String userBaseDn = composeDn(ldapConfiguration.getUserSearchBase(), ldapConfiguration.getBaseDn()); - int memberCount = 0; - for (String memberId : groupMemberList) { - if (userList.size() < query.getMaxResults() && memberCount >= query.getFirstResult()) { - if (ldapConfiguration.isUsePosixGroups()) { - query.userId(memberId); - } - List users = ldapConfiguration.isUsePosixGroups() ? - findUsersWithoutGroupId(query, userBaseDn, true) : - findUsersWithoutGroupId(query, memberId, true); - if (!users.isEmpty()) { - userList.add(users.get(0)); - } - } - memberCount++; - } - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("; Result size()="); - resultLogger.append(userList.size()); - resultLogger.append(" FirstResult="); - resultLogger.append(userList.isEmpty() ? "--" : userList.get(0).getFirstName() + "]"); - } - return userList; - - } catch (NamingException e) { - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("; Exception "); - resultLogger.append(e); - } - throw new IdentityProviderException("Could not query for users " + resultLogger, e); - } finally { - try { - if (enumeration != null) { - enumeration.close(); - } - } catch (Exception e) { - // ignore silently - } - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("]"); - LdapPluginLogger.INSTANCE.userQueryResult(resultLogger.toString()); } + } while (paginationContinues(groupMembers.size(), query.getMaxResults())); - } - } - - public List findUsersWithoutGroupId(LdapUserQueryImpl query, String userBaseDn, boolean ignorePagination) { - StringBuilder resultLogger = new StringBuilder(); - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("findUsersWithoutGroupId: from "); - resultLogger.append(query.getFirstResult()); - } - - NamingEnumeration enumeration = null; - try { - initializeControls(query, resultLogger); - - List userList = new ArrayList<>(); - int resultCount = 0; - int pageNumber = 0; - - do { - String filter = getUserSearchFilter(query); - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append(" search userBaseDn["); - resultLogger.append(userBaseDn); - resultLogger.append("] filter["); - resultLogger.append(filter); - resultLogger.append("];"); - } - enumeration = initialContext.search(userBaseDn, filter, ldapConfiguration.getSearchControls()); - - pageNumber++; - // perform client-side paging - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append(" (page:"); - resultLogger.append(pageNumber); - resultLogger.append(") "); + List userList = new ArrayList<>(); + String userBaseDn = composeDn(ldapConfiguration.getUserSearchBase(), ldapConfiguration.getBaseDn()); + int memberCount = 0; + for (String memberId : groupMembers) { + if (userList.size() < query.getMaxResults() && memberCount >= query.getFirstResult()) { + if (ldapConfiguration.isUsePosixGroups()) { + query.userId(memberId); } - - while (enumeration.hasMoreElements() - && (userList.size() < query.getMaxResults() || ignorePagination)) { - SearchResult result = enumeration.nextElement(); - - UserEntity user = transformUser(result); - - String userId = user.getId(); - - if (userId == null) { - LdapPluginLogger.INSTANCE.invalidLdapUserReturned(user, result); - } else { - if (isAuthenticatedUser(user) || isAuthorized(READ, USER, userId)) { - - if (resultCount >= query.getFirstResult() || ignorePagination) { - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("id="); - resultLogger.append(user.getId()); - resultLogger.append(", firstName="); - resultLogger.append(user.getFirstName()); - resultLogger.append(", lastName="); - resultLogger.append(user.getLastName()); - - resultLogger.append(" based on "); - resultLogger.append(result); - resultLogger.append(", "); - } - userList.add(user); - } - resultCount++; - } - } + List users = ldapConfiguration.isUsePosixGroups() ? + findUsersWithoutGroupId(query, userBaseDn, true) : + findUsersWithoutGroupId(query, memberId, true); + if (!users.isEmpty()) { + userList.add(users.get(0)); } - } while (isNextPageDetected(resultLogger) && userList.size() < query.getMaxResults()); - - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append(";Result size()="); - resultLogger.append(userList.size()); - resultLogger.append(" First["); - resultLogger.append(userList.isEmpty() ? "--" : userList.get(0).getFirstName() + "]"); - } - return userList; - - } catch (NamingException e) { - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append(";Exception: "); - resultLogger.append(e); - } - throw new IdentityProviderException("Could not query for users " + resultLogger, e); - } finally { - try { - if (enumeration != null) { - enumeration.close(); - } - } catch (Exception e) { - // ignore silently - } - - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("]"); - LdapPluginLogger.INSTANCE.userQueryResult(resultLogger.toString()); } - + memberCount++; } + + return userList; } public boolean checkPassword(String userId, String password) { @@ -400,10 +204,10 @@ public boolean checkPassword(String userId, String password) { /* * We only allow login with no password if anonymous login is set. - * RFC allows such a behavior but discourages the usage so we provide it for - * user which have an ldap with anonymous login. + * RFC allows such a behavior but discourages the usage, so we provide it for + * user which have a ldap with anonymous login. */ - if (!ldapConfiguration.isAllowAnonymousLogin() && password.equals("")) { + if (!ldapConfiguration.isAllowAnonymousLogin() && password.isEmpty()) { return false; } @@ -419,7 +223,7 @@ public boolean checkPassword(String userId, String password) { try { // bind authenticate for user + supplied password - context = openContext(user.getDn(), password); + context = ldapClient.openContext(user.getDn(), password); return true; } catch (LdapAuthenticationException e) { @@ -430,7 +234,7 @@ public boolean checkPassword(String userId, String password) { } } finally { - closeLdapCtx(context); + ldapClient.closeLdapCtx(context); } @@ -481,13 +285,26 @@ protected String getUserSearchFilter(LdapUserQueryImpl query) { return search.toString(); } + protected boolean isAuthenticatedAndAuthorized(String userId) { + return isAuthenticatedUser(userId) || isAuthorizedToRead(USER, userId); + } + + public List findUsersWithoutGroupId(LdapUserQueryImpl query, String userBaseDn, boolean ignorePagination) { + initializeControls(query); + + return retrieveResults(userBaseDn, + getUserSearchFilter(query), + this::transformUser, + this::isAuthenticatedAndAuthorized, + query.getMaxResults(), + query.getFirstResult(), + ignorePagination); + } // Groups /////////////////////////////////////////////// public Group findGroupById(String groupId) { - return createGroupQuery(getCommandContext()) - .groupId(groupId) - .singleResult(); + return createGroupQuery(getCommandContext()).groupId(groupId).singleResult(); } public GroupQuery createGroupQuery() { @@ -499,10 +316,14 @@ public GroupQuery createGroupQuery(CommandContext commandContext) { } public long findGroupCountByQueryCriteria(LdapGroupQuery ldapGroupQuery) { - ensureContextInitialized(); + ldapClient.ensureContextInitialized(); return findGroupByQueryCriteria(ldapGroupQuery).size(); } + protected boolean isAuthorizedToReadGroup(String groupId) { + return isAuthorizedToRead(GROUP, groupId); + } + public List findGroupByQueryCriteria(LdapGroupQuery query) { // convert DB wildcards to LDAP wildcards if necessary @@ -510,91 +331,19 @@ public List findGroupByQueryCriteria(LdapGroupQuery query) { query.groupNameLike(query.getNameLike().replaceAll(DB_QUERY_WILDCARD, LDAP_QUERY_WILDCARD)); } - StringBuilder resultLogger = new StringBuilder(); - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("findGroupByQueryCriteria: from "); - resultLogger.append(query.getFirstResult()); - } - ensureContextInitialized(); + ldapClient.ensureContextInitialized(); String groupBaseDn = composeDn(ldapConfiguration.getGroupSearchBase(), ldapConfiguration.getBaseDn()); - NamingEnumeration enumeration = null; - - try { - initializeControls(query, resultLogger); - String filter = getGroupSearchFilter(query); - // perform client-side paging - List groupList = new ArrayList<>(); - int resultCount = 0; - int pageNumber = 0; - - do { - enumeration = initialContext.search(groupBaseDn, filter, ldapConfiguration.getSearchControls()); - pageNumber++; - - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("; (page:"); - resultLogger.append(pageNumber); - resultLogger.append(") ["); - } - - while (enumeration.hasMoreElements() && groupList.size() < query.getMaxResults()) { - SearchResult result = enumeration.nextElement(); - - GroupEntity group = transformGroup(result); + initializeControls(query); - String groupId = group.getId(); - - if (groupId == null) { - LdapPluginLogger.INSTANCE.invalidLdapGroupReturned(group, result); - } else { - if (isAuthorized(READ, GROUP, groupId)) { - - if (resultCount >= query.getFirstResult()) { - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append(group); - resultLogger.append(" based on "); - resultLogger.append(result); - resultLogger.append(", "); - } - groupList.add(group); - } - resultCount++; - } - } - } - } while (isNextPageDetected(resultLogger) && groupList.size() < query.getMaxResults()); - - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("; Result size()="); - resultLogger.append(groupList.size()); - resultLogger.append(" FirstResult="); - resultLogger.append(groupList.isEmpty() ? "--" : groupList.get(0).getName() + "]"); - } - return groupList; - - } catch (NamingException e) { - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("; Exception "); - resultLogger.append(e); - } - throw new IdentityProviderException("Could not query for users " + resultLogger, e); - - } finally { - try { - if (enumeration != null) { - enumeration.close(); - } - } catch (Exception e) { - // ignore silently - } - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("]"); - LdapPluginLogger.INSTANCE.groupQueryResult(resultLogger.toString()); - } - - } + return retrieveResults(groupBaseDn, + getGroupSearchFilter(query), + this::transformGroup, + this::isAuthorizedToReadGroup, + query.getMaxResults(), + query.getFirstResult(), + false); } protected String getGroupSearchFilter(LdapGroupQuery query) { @@ -638,10 +387,60 @@ protected String getGroupSearchFilter(LdapGroupQuery query) { // Utils //////////////////////////////////////////// + @SuppressWarnings("unchecked") + protected List retrieveResults(String baseDn, + String filter, + Function transformEntity, + Predicate resultCountPredicate, + int maxResults, + int firstResult, + boolean ignorePagination) { + StringBuilder resultLogger = new StringBuilder(); + if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { + resultLogger.append("LDAP query results: ["); + } + + List entities = new ArrayList<>(); + + int resultCount = 0; + + do { + try (LdapSearchResults searchResults = ldapClient.search(baseDn, filter)) { + while (searchResults.hasMoreElements() && (entities.size() < maxResults || ignorePagination)) { + SearchResult result = searchResults.nextElement(); + + E entity = transformEntity.apply(result); + + String id = entity.getId(); + if (id != null && resultCountPredicate.test(id)) { + if (resultCount >= firstResult || ignorePagination) { + if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { + resultLogger.append(entity); + resultLogger.append(" based on "); + resultLogger.append(result); + resultLogger.append(", "); + } + entities.add((T) entity); + } + resultCount++; + } else { + LdapPluginLogger.INSTANCE.invalidLdapEntityReturned(entity, result); + + } + } + } + } while (paginationContinues(entities.size(), maxResults)); + + if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { + resultLogger.append("]"); + LdapPluginLogger.INSTANCE.queryResult(resultLogger.toString()); + } + + return entities; + } + protected String getDnForUser(String userId) { - LdapUserEntity user = (LdapUserEntity) createUserQuery(getCommandContext()) - .userId(userId) - .singleResult(); + LdapUserEntity user = (LdapUserEntity) createUserQuery(getCommandContext()).userId(userId).singleResult(); if (user == null) { return ""; } else { @@ -650,9 +449,7 @@ protected String getDnForUser(String userId) { } protected String getDnForGroup(String groupId) { - LdapGroupEntity group = (LdapGroupEntity) createGroupQuery(getCommandContext()) - .groupId(groupId) - .singleResult(); + LdapGroupEntity group = (LdapGroupEntity) createGroupQuery(getCommandContext()).groupId(groupId).singleResult(); if (group == null) { return ""; } else { @@ -660,15 +457,6 @@ protected String getDnForGroup(String groupId) { } } - protected String getStringAttributeValue(String attrName, Attributes attributes) throws NamingException { - Attribute attribute = attributes.get(attrName); - if (attribute != null) { - return (String) attribute.get(); - } else { - return null; - } - } - protected void addFilter(String attributeName, String attributeValue, StringWriter writer) { writer.write("("); writer.write(attributeName); @@ -677,24 +465,24 @@ protected void addFilter(String attributeName, String attributeValue, StringWrit writer.write(")"); } - protected LdapUserEntity transformUser(SearchResult result) throws NamingException { + protected UserEntity transformUser(SearchResult result) { final Attributes attributes = result.getAttributes(); LdapUserEntity user = new LdapUserEntity(); user.setDn(result.getNameInNamespace()); - user.setId(getStringAttributeValue(ldapConfiguration.getUserIdAttribute(), attributes)); - user.setFirstName(getStringAttributeValue(ldapConfiguration.getUserFirstnameAttribute(), attributes)); - user.setLastName(getStringAttributeValue(ldapConfiguration.getUserLastnameAttribute(), attributes)); - user.setEmail(getStringAttributeValue(ldapConfiguration.getUserEmailAttribute(), attributes)); + user.setId(LdapClient.getValue(ldapConfiguration.getUserIdAttribute(), attributes)); + user.setFirstName(LdapClient.getValue(ldapConfiguration.getUserFirstnameAttribute(), attributes)); + user.setLastName(LdapClient.getValue(ldapConfiguration.getUserLastnameAttribute(), attributes)); + user.setEmail(LdapClient.getValue(ldapConfiguration.getUserEmailAttribute(), attributes)); return user; } - protected GroupEntity transformGroup(SearchResult result) throws NamingException { + protected GroupEntity transformGroup(SearchResult result) { final Attributes attributes = result.getAttributes(); LdapGroupEntity group = new LdapGroupEntity(); group.setDn(result.getNameInNamespace()); - group.setId(getStringAttributeValue(ldapConfiguration.getGroupIdAttribute(), attributes)); - group.setName(getStringAttributeValue(ldapConfiguration.getGroupNameAttribute(), attributes)); - group.setType(getStringAttributeValue(ldapConfiguration.getGroupTypeAttribute(), attributes)); + group.setId(LdapClient.getValue(ldapConfiguration.getGroupIdAttribute(), attributes)); + group.setName(LdapClient.getValue(ldapConfiguration.getGroupNameAttribute(), attributes)); + group.setType(LdapClient.getValue(ldapConfiguration.getGroupTypeAttribute(), attributes)); return group; } @@ -704,64 +492,54 @@ protected GroupEntity transformGroup(SearchResult result) throws NamingException * @param query query asks, contains the order by requested * @return list of control to send to LDAP */ - protected List getSortingControls(AbstractQuery query, StringBuilder resultLogger) { + protected List getSortingControls(AbstractQuery query) { + List controls = new ArrayList<>(); - try { - List controls = new ArrayList<>(); - - List orderBy = query.getOrderingProperties(); - if (orderBy != null) { - for (QueryOrderingProperty orderingProperty : orderBy) { - String propertyName = orderingProperty.getQueryProperty().getName(); - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append(", OrderBy["); - resultLogger.append(propertyName); - resultLogger.append("-"); - resultLogger.append(orderingProperty.getDirection() == null ? "no_direction(desc)" : orderingProperty.getDirection().getName()); - resultLogger.append("]"); - } - SortKey sortKey = null; - if (query instanceof LdapUserQueryImpl) { - if (UserQueryProperty.USER_ID.getName().equals(propertyName)) { - sortKey = new SortKey(ldapConfiguration.getUserIdAttribute(), Direction.ASCENDING.equals(orderingProperty.getDirection()), - null); - - } else if (UserQueryProperty.EMAIL.getName().equals(propertyName)) { - sortKey = new SortKey(ldapConfiguration.getUserEmailAttribute(), Direction.ASCENDING.equals(orderingProperty.getDirection()), - null); - - } else if (UserQueryProperty.FIRST_NAME.getName().equals(propertyName)) { - sortKey = new SortKey(ldapConfiguration.getUserFirstnameAttribute(), Direction.ASCENDING.equals(orderingProperty.getDirection()), - null); - - } else if (UserQueryProperty.LAST_NAME.getName().equals(propertyName)) { - sortKey = new SortKey(ldapConfiguration.getUserLastnameAttribute(), Direction.ASCENDING.equals(orderingProperty.getDirection()), - null); - } - } else if (query instanceof LdapGroupQuery) { - if (GroupQueryProperty.GROUP_ID.getName().equals(propertyName)) { - sortKey = new SortKey(ldapConfiguration.getGroupIdAttribute(), - Direction.ASCENDING.equals(orderingProperty.getDirection()), - null); - } else if (GroupQueryProperty.NAME.getName().equals(propertyName)) { - sortKey = new SortKey(ldapConfiguration.getGroupNameAttribute(), - Direction.ASCENDING.equals(orderingProperty.getDirection()), - null); - } - // not possible to order by Type: LDAP may not support the type - } + List orderBy = query.getOrderingProperties(); + if (orderBy != null) { + for (QueryOrderingProperty orderingProperty : orderBy) { + String propertyName = orderingProperty.getQueryProperty().getName(); + SortKey sortKey = getSortKey(query, propertyName, orderingProperty); - if (sortKey != null) { - controls.add(new SortControl(new SortKey[] { sortKey }, Control.CRITICAL)); - } + if (sortKey != null) { + LdapClient.addSortKey(sortKey, controls); } } + } - return controls; + return controls; + } - } catch (IOException e) { - throw new IdentityProviderException("Exception while setting paging settings", e); + protected SortKey getSortKey(AbstractQuery query, String propertyName, QueryOrderingProperty orderingProperty) { + if (query instanceof LdapUserQueryImpl) { + if (UserQueryProperty.USER_ID.getName().equals(propertyName)) { + return new SortKey(ldapConfiguration.getUserIdAttribute(), + Direction.ASCENDING.equals(orderingProperty.getDirection()), null); + + } else if (UserQueryProperty.EMAIL.getName().equals(propertyName)) { + return new SortKey(ldapConfiguration.getUserEmailAttribute(), + Direction.ASCENDING.equals(orderingProperty.getDirection()), null); + + } else if (UserQueryProperty.FIRST_NAME.getName().equals(propertyName)) { + return new SortKey(ldapConfiguration.getUserFirstnameAttribute(), + Direction.ASCENDING.equals(orderingProperty.getDirection()), null); + + } else if (UserQueryProperty.LAST_NAME.getName().equals(propertyName)) { + return new SortKey(ldapConfiguration.getUserLastnameAttribute(), + Direction.ASCENDING.equals(orderingProperty.getDirection()), null); + } + } else if (query instanceof LdapGroupQuery) { + if (GroupQueryProperty.GROUP_ID.getName().equals(propertyName)) { + return new SortKey(ldapConfiguration.getGroupIdAttribute(), + Direction.ASCENDING.equals(orderingProperty.getDirection()), null); + } else if (GroupQueryProperty.NAME.getName().equals(propertyName)) { + return new SortKey(ldapConfiguration.getGroupNameAttribute(), + Direction.ASCENDING.equals(orderingProperty.getDirection()), null); + } + // not possible to order by Type: LDAP may not support the type } + + return null; } protected String composeDn(String... parts) { @@ -789,17 +567,17 @@ protected String composeDn(String... parts) { /** * @return true if the passed-in user is currently authenticated */ - protected boolean isAuthenticatedUser(UserEntity user) { - if (user.getId() == null) { + protected boolean isAuthenticatedUser(String userid) { + if (userid == null) { return false; } - return user.getId().equalsIgnoreCase(getCommandContext().getAuthenticatedUserId()); + return userid.equalsIgnoreCase(getCommandContext().getAuthenticatedUserId()); } - protected boolean isAuthorized(Permission permission, Resource resource, String resourceId) { - return !ldapConfiguration.isAuthorizationCheckEnabled() || getCommandContext() - .getAuthorizationManager() - .isAuthorized(permission, resource, resourceId); + protected boolean isAuthorizedToRead(Resource resource, String resourceId) { + return !ldapConfiguration.isAuthorizationCheckEnabled() || + getCommandContext().getAuthorizationManager() + .isAuthorized(READ, resource, resourceId); } // Based on https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java @@ -808,23 +586,23 @@ protected final String escapeLDAPSearchFilter(String filter) { for (int i = 0; i < filter.length(); i++) { char curChar = filter.charAt(i); switch (curChar) { - case '\\': - sb.append("\\5c"); - break; - case '*': - sb.append("\\2a"); - break; - case '(': - sb.append("\\28"); - break; - case ')': - sb.append("\\29"); - break; - case '\u0000': - sb.append("\\00"); - break; - default: - sb.append(curChar); + case '\\': + sb.append("\\5c"); + break; + case '*': + sb.append("\\2a"); + break; + case '(': + sb.append("\\28"); + break; + case ')': + sb.append("\\29"); + break; + case '\u0000': + sb.append("\\00"); + break; + default: + sb.append(curChar); } } return sb.toString(); @@ -833,55 +611,21 @@ protected final String escapeLDAPSearchFilter(String filter) { /** * Initializes paged results and sort controls. Might not be supported by all LDAP implementations. */ - protected void initializeControls(AbstractQuery query, StringBuilder resultLogger) throws NamingException { - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append(query.getFirstResult()); - - resultLogger.append(" "); - resultLogger.append(query); - resultLogger.append(" "); - - - resultLogger.append(ldapConfiguration.isSortControlSupported() ? " -sort-" : "-nosort-"); - - if (!isPaginationSupported()) { - resultLogger.append(" -noPagination"); - - } else { - resultLogger.append(" -pagination("); - resultLogger.append(ldapConfiguration.getPageSize()); - resultLogger.append(")"); - } - } - + protected void initializeControls(AbstractQuery query) { List listControls = new ArrayList<>(); if (ldapConfiguration.isSortControlSupported()) { - listControls.addAll(getSortingControls(query, resultLogger)); + listControls.addAll(getSortingControls(query)); } try { if (isPaginationSupported()) { - listControls.add(new PagedResultsControl(getPageSize(), Control.NONCRITICAL)); + LdapClient.addPaginationControl(listControls, null, getPageSize()); } + } catch (IdentityProviderException ignored) { + // Ignore exception when pagination is not supported + } finally { if (!listControls.isEmpty()) { - initialContext.setRequestControls(listControls.toArray(new Control[0])); - } - } catch (NamingException | IOException e) { - // page control is not supported by this LDAP - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("Unsupported page Control;"); - } - // set supported list controls - if (!listControls.isEmpty()) { - try { - initialContext.setRequestControls(listControls.toArray(new Control[0])); - } catch (NamingException ne) { - // this exception is not related to the page control, so throw it - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("Unsupported Control;"); - } - throw ne; - } + ldapClient.setRequestControls(listControls); } } } @@ -889,73 +633,45 @@ protected void initializeControls(AbstractQuery query, StringBuilder resul /** * Check in the context if we reach the last page on the query * - * @param resultLogger Logger to send information * @return new page detected */ - protected boolean isNextPageDetected(StringBuilder resultLogger) { + protected boolean nextPageDetected() { // if the pagination is not activated, there isn't a next page. if (!isPaginationSupported()) { return false; } - try { - Control[] controls = initialContext.getResponseControls(); - if (controls == null) { - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("No-controls-from-the-server"); - } - return false; - } + Control[] controls = ldapClient.getResponseControls(); + if (controls == null) { + return false; + } - List newControlList = new ArrayList<>(); - boolean newPageDetected = false; - for (Control control : controls) { - if (control instanceof PagedResultsResponseControl) { - PagedResultsResponseControl prrc = (PagedResultsResponseControl) control; - byte[] cookie = prrc.getCookie(); + List newControlList = new ArrayList<>(); + boolean newPageDetected = false; + for (Control control : controls) { + if (control instanceof PagedResultsResponseControl) { + PagedResultsResponseControl prrc = (PagedResultsResponseControl) control; + byte[] cookie = prrc.getCookie(); - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("; End-of-page? "); - resultLogger.append((cookie == null ? "No-more-page" : "Next-page-detected")); - } - // Re-activate paged results - try { - newControlList.add(new PagedResultsControl(getPageSize(), cookie, Control.CRITICAL)); - } catch (IOException e) { - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("Error-when-set-again-the-new-cookie "); - resultLogger.append(e); - } - // stop to loop - return false; - } - newPageDetected = cookie != null; + // Re-activate paged results + try { + LdapClient.addPaginationControl(newControlList, cookie, getPageSize()); + } catch (IdentityProviderException e) { + return false; } + newPageDetected = cookie != null; } - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("; SetAgain controlList.size()="); - resultLogger.append(newControlList.size()); - } - if (!newControlList.isEmpty()) { - // This is more an UPDATE than a SET. All previous control (sorting) does not need to - // be set again, the current context keeps them, and the next page is correctly ordered for example. - // In the current context, just the PageResultControl must be re-set (even this a xxxResultxxx) - // All another result (SortResponseControl for example) must not be set again, nor the SortControl. - initialContext.setRequestControls(newControlList.toArray(new Control[0])); - } - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("; Done. NewPageDetected="); - resultLogger.append(newPageDetected); - } + } - return newPageDetected; - } catch (NamingException ne) { - if (LdapPluginLogger.INSTANCE.isDebugEnabled()) { - resultLogger.append("Could not manage ResponseControl; "); - resultLogger.append(ne); - } - return false; + if (!newControlList.isEmpty()) { + // This is more an UPDATE than a SET. All previous control (sorting) does not need to + // be set again, the current context keeps them, and the next page is correctly ordered for example. + // In the current context, just the PageResultControl must be re-set (even this a xxxResultxxx) + // All another result (SortResponseControl for example) must not be set again, nor the SortControl. + ldapClient.setRequestControls(newControlList); } + + return newPageDetected; } protected boolean isPaginationSupported() { @@ -986,4 +702,5 @@ public Tenant findTenantById(String id) { // since multi-tenancy is not supported for the LDAP plugin, always return null return null; } + } diff --git a/engine-plugins/identity-ldap/src/main/java/org/camunda/bpm/identity/impl/ldap/LdapSearchResults.java b/engine-plugins/identity-ldap/src/main/java/org/camunda/bpm/identity/impl/ldap/LdapSearchResults.java new file mode 100644 index 00000000000..6ef29e9708c --- /dev/null +++ b/engine-plugins/identity-ldap/src/main/java/org/camunda/bpm/identity/impl/ldap/LdapSearchResults.java @@ -0,0 +1,62 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; 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.camunda.bpm.identity.impl.ldap; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.SearchResult; + +public class LdapSearchResults implements NamingEnumeration, AutoCloseable { + + protected NamingEnumeration enumeration; + + public LdapSearchResults(NamingEnumeration enumeration) { + this.enumeration = enumeration; + } + + @Override + public SearchResult next() throws NamingException { + return enumeration.next(); + } + + @Override + public boolean hasMore() throws NamingException { + return enumeration.hasMore(); + } + + @Override + public boolean hasMoreElements() { + return enumeration.hasMoreElements(); + } + + @Override + public SearchResult nextElement() { + return enumeration.nextElement(); + } + + @Override + public void close() { + try { + if (enumeration != null) { + enumeration.close(); + } + } catch (Exception e) { + // ignore silently + } + } + +} diff --git a/engine-plugins/identity-ldap/src/main/java/org/camunda/bpm/identity/impl/ldap/util/LdapPluginLogger.java b/engine-plugins/identity-ldap/src/main/java/org/camunda/bpm/identity/impl/ldap/util/LdapPluginLogger.java index b48df90957b..e52c6dd5ce1 100644 --- a/engine-plugins/identity-ldap/src/main/java/org/camunda/bpm/identity/impl/ldap/util/LdapPluginLogger.java +++ b/engine-plugins/identity-ldap/src/main/java/org/camunda/bpm/identity/impl/ldap/util/LdapPluginLogger.java @@ -18,7 +18,7 @@ import javax.naming.directory.SearchResult; -import org.camunda.bpm.engine.impl.persistence.entity.GroupEntity; +import org.camunda.bpm.engine.impl.db.DbEntity; import org.camunda.bpm.engine.impl.persistence.entity.UserEntity; import org.camunda.commons.logging.BaseLogger; @@ -33,48 +33,29 @@ public class LdapPluginLogger extends BaseLogger { public static final LdapPluginLogger INSTANCE = BaseLogger.createLogger( LdapPluginLogger.class, PROJECT_CODE, "org.camunda.bpm.identity.impl.ldap", "00"); - public void pluginActivated(String pluginClassName, String engineName) - { + public void pluginActivated(String pluginClassName, String engineName) { logInfo("001", "PLUGIN {} activated on process engine {}", pluginClassName, engineName); } - public void acceptingUntrustedCertificates() - { + public void acceptingUntrustedCertificates() { logWarn("002", "Enabling accept of untrusted certificates. Use at own risk."); } - public void exceptionWhenClosingLdapCOntext(Exception e) - { + public void exceptionWhenClosingLdapContext(Exception e) { logDebug("003", "exception while closing LDAP DIR CTX", e); } - public void invalidLdapGroupReturned(GroupEntity group, SearchResult searchResult) - { - logError("004", "LDAP group query returned a group with id null. This group will be ignored. " + public void invalidLdapEntityReturned(E entity, SearchResult searchResult) { + String entityType = entity instanceof UserEntity ? "user" : "group"; + logError("004", "LDAP {} query returned a {} with id null. This {} will be ignored. " + "This indicates a misconfiguration of the LDAP plugin or a problem with the LDAP service." - + " Enable DEBUG/FINE logging for details."); + + " Enable DEBUG/FINE logging for details.", entityType, entityType, entityType); // log sensitive data only on FINE - logDebug("004", "Invalid group: {} based on search result {}", group, searchResult); + logDebug("004", "Invalid {}: {} based on search result {}", entityType, entity, searchResult); } - public void invalidLdapUserReturned(UserEntity user, SearchResult searchResult) - { - logError("004", "LDAP user query returned a user with id null. This user will be ignored. " - + "This indicates a misconfiguration of the LDAP plugin or a problem with the LDAP service." - + " Enable DEBUG/FINE logging for details."); - // log sensitive data only on FINE - logDebug("004", "Invalid user: {} based on search result {}", user, searchResult); - } - - public void groupQueryResult(String summary) - { + public void queryResult(String summary) { // log sensitive data only on FINE logDebug("005", summary); } - - public void userQueryResult(String summary) - { - // log sensitive data only on FINE - logDebug("006", summary); - } }