
pro.taskana.common.rest.ldap.LdapClient Maven / Gradle / Ivy
package pro.taskana.common.rest.ldap;
import static java.util.function.Predicate.not;
import jakarta.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.naming.InvalidNameException;
import javax.naming.directory.SearchControls;
import javax.naming.ldap.LdapName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.AbstractContextMapper;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.NotPresentFilter;
import org.springframework.ldap.filter.OrFilter;
import org.springframework.ldap.filter.PresentFilter;
import org.springframework.ldap.filter.WhitespaceWildcardsFilter;
import org.springframework.ldap.support.LdapNameBuilder;
import org.springframework.stereotype.Component;
import pro.taskana.TaskanaConfiguration;
import pro.taskana.common.api.TaskanaRole;
import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.internal.util.LogSanitizer;
import pro.taskana.common.rest.models.AccessIdRepresentationModel;
import pro.taskana.user.api.models.User;
import pro.taskana.user.internal.models.UserImpl;
/** Class for Ldap access. */
@Component
public class LdapClient {
private static final Logger LOGGER = LoggerFactory.getLogger(LdapClient.class);
private static final String CN = "cn";
private final TaskanaConfiguration taskanaConfiguration;
private final Environment env;
private final LdapTemplate ldapTemplate;
private final boolean useLowerCaseForAccessIds;
private boolean active = false;
private int minSearchForLength;
private int maxNumberOfReturnedAccessIds;
private String message;
@Autowired
public LdapClient(
Environment env, LdapTemplate ldapTemplate, TaskanaConfiguration taskanaConfiguration) {
this.env = env;
this.ldapTemplate = ldapTemplate;
this.taskanaConfiguration = taskanaConfiguration;
this.useLowerCaseForAccessIds = TaskanaConfiguration.shouldUseLowerCaseForAccessIds();
}
/**
* Search LDAP for matching users or groups or permissions.
*
* @param name lookup string for names or groups or permissions
* @return a list of AccessIdResources sorted by AccessId and limited to
* maxNumberOfReturnedAccessIds
* @throws InvalidArgumentException if input is shorter than minSearchForLength
* @throws InvalidNameException thrown if name is not a valid dn
*/
public List searchUsersAndGroupsAndPermissions(final String name)
throws InvalidArgumentException, InvalidNameException {
isInitOrFail();
testMinSearchForLength(name);
List accessIds = new ArrayList<>();
if (nameIsDn(name)) {
AccessIdRepresentationModel groupByDn = searchAccessIdByDn(name);
if (groupByDn != null) {
accessIds.add(groupByDn);
}
} else {
accessIds.addAll(searchUsersByNameOrAccessId(name));
accessIds.addAll(searchGroupsByName(name));
accessIds.addAll(searchPermissionsByName(name));
}
sortListOfAccessIdResources(accessIds);
return getFirstPageOfaResultList(accessIds);
}
public List searchUsersByNameOrAccessIdInUserRole(
final String nameOrAccessId) throws InvalidArgumentException {
LOGGER.debug(
"entry to searchUsersByNameOrAccessIdInUserRoleGroups(nameOrAccessId = {}).",
LogSanitizer.stripLineBreakingChars(nameOrAccessId));
isInitOrFail();
testMinSearchForLength(nameOrAccessId);
final OrFilter userDetailsOrFilter = new OrFilter();
userDetailsOrFilter.or(
new WhitespaceWildcardsFilter(getUserFirstnameAttribute(), nameOrAccessId));
userDetailsOrFilter.or(
new WhitespaceWildcardsFilter(getUserLastnameAttribute(), nameOrAccessId));
userDetailsOrFilter.or(
new WhitespaceWildcardsFilter(getUserFullnameAttribute(), nameOrAccessId));
userDetailsOrFilter.or(new WhitespaceWildcardsFilter(getUserIdAttribute(), nameOrAccessId));
Set userGroups = taskanaConfiguration.getRoleMap().get(TaskanaRole.USER);
final OrFilter groupMembershipOrFilter = new OrFilter();
userGroups.forEach(
group ->
groupMembershipOrFilter.or(new EqualsFilter(getUserMemberOfGroupAttribute(), group)));
final AndFilter andFilter = new AndFilter();
andFilter.and(userDetailsOrFilter);
andFilter.and(groupMembershipOrFilter);
andFilter.and(new EqualsFilter(getUserSearchFilterName(), getUserSearchFilterValue()));
final List accessIds =
ldapTemplate.search(
getUserSearchBase(),
andFilter.encode(),
SearchControls.SUBTREE_SCOPE,
getLookUpUserAttributesToReturn(),
new UserContextMapper());
LOGGER.debug(
"exit from searchUsersByNameOrAccessIdInUserRoleGroups. Retrieved the following users: {}.",
accessIds);
return accessIds;
}
public List searchUsersInUserRole() {
Set userGroupsOrUser = taskanaConfiguration.getRoleMap().get(TaskanaRole.USER);
final OrFilter userOrGroupFilter = new OrFilter();
userGroupsOrUser.forEach(
userOrGroup -> {
userOrGroupFilter.or(new EqualsFilter(getUserMemberOfGroupAttribute(), userOrGroup));
userOrGroupFilter.or(new EqualsFilter(getUserIdAttribute(), userOrGroup));
});
final List users =
ldapTemplate.search(
getUserSearchBase(),
userOrGroupFilter.encode(),
SearchControls.SUBTREE_SCOPE,
getLookUpUserInfoAttributesToReturn(),
new UserInfoContextMapper());
LOGGER.debug("exit from searchUsersInUserRole. Retrieved the following users: {}.", users);
return users;
}
public List searchUsersByNameOrAccessId(final String name)
throws InvalidArgumentException {
isInitOrFail();
testMinSearchForLength(name);
final AndFilter andFilter = new AndFilter();
andFilter.and(new EqualsFilter(getUserSearchFilterName(), getUserSearchFilterValue()));
final OrFilter orFilter = new OrFilter();
orFilter.or(new WhitespaceWildcardsFilter(getUserFirstnameAttribute(), name));
orFilter.or(new WhitespaceWildcardsFilter(getUserLastnameAttribute(), name));
orFilter.or(new WhitespaceWildcardsFilter(getUserFullnameAttribute(), name));
orFilter.or(new WhitespaceWildcardsFilter(getUserIdAttribute(), name));
andFilter.and(orFilter);
LOGGER.debug("Using filter '{}' for LDAP query.", andFilter);
return ldapTemplate.search(
getUserSearchBase(),
andFilter.encode(),
SearchControls.SUBTREE_SCOPE,
getLookUpUserAttributesToReturn(),
new UserContextMapper());
}
public List getUsersByAccessId(final String accessId) {
isInitOrFail();
final AndFilter andFilter = new AndFilter();
andFilter.and(new EqualsFilter(getUserSearchFilterName(), getUserSearchFilterValue()));
andFilter.and(new EqualsFilter(getUserIdAttribute(), accessId));
String[] userAttributesToReturn = {
getUserFirstnameAttribute(), getUserLastnameAttribute(), getUserIdAttribute()
};
LOGGER.debug("Using filter '{}' for LDAP query.", andFilter);
return ldapTemplate.search(
getUserSearchBase(),
andFilter.encode(),
SearchControls.SUBTREE_SCOPE,
userAttributesToReturn,
new UserContextMapper());
}
public List searchGroupsByName(final String name)
throws InvalidArgumentException {
isInitOrFail();
testMinSearchForLength(name);
final AndFilter andFilter = new AndFilter();
andFilter.and(new EqualsFilter(getGroupSearchFilterName(), getGroupSearchFilterValue()));
final OrFilter orFilter = new OrFilter();
orFilter.or(new WhitespaceWildcardsFilter(getGroupNameAttribute(), name));
if (!CN.equals(getGroupNameAttribute())) {
orFilter.or(new WhitespaceWildcardsFilter(CN, name));
}
final AndFilter andFilter2 = new AndFilter();
andFilter2.and(new NotPresentFilter(getUserPermissionsAttribute()));
andFilter.and(orFilter);
andFilter2.and(andFilter);
LOGGER.debug("Using filter '{}' for LDAP query.", andFilter);
return ldapTemplate.search(
getGroupSearchBase(),
andFilter2.encode(),
SearchControls.SUBTREE_SCOPE,
getLookUpGroupAttributesToReturn(),
new GroupContextMapper());
}
public Map> searchAccessIdForGroupsAndPermissionsByDn(List dns)
throws InvalidNameException {
List accessIdsOfGroupsAndPermissions = new ArrayList<>();
List permissions = new ArrayList<>();
List accessIdsOfPermissions = new ArrayList<>();
for (String groupOrPermission : dns) {
accessIdsOfGroupsAndPermissions.add(searchAccessIdByDn(groupOrPermission)
.getAccessId());
}
for (String groupOrPermission : accessIdsOfGroupsAndPermissions) {
permissions.addAll(searchPermissionsByName(groupOrPermission));
}
for (AccessIdRepresentationModel permission : permissions) {
accessIdsOfPermissions.add(permission.getAccessId());
}
List accessIdsOfGroups = new ArrayList<>(accessIdsOfGroupsAndPermissions);
accessIdsOfGroups.removeAll(accessIdsOfPermissions);
Map> map = new HashMap<>();
map.put("permissions", accessIdsOfPermissions);
map.put("groups", accessIdsOfGroups);
return map;
}
public List searchPermissionsByName(final String name)
throws InvalidArgumentException {
isInitOrFail();
testMinSearchForLength(name);
final AndFilter andFilter = new AndFilter();
andFilter.and(new EqualsFilter(getPermissionSearchFilterName(),
getPermissionSearchFilterValue()));
final OrFilter orFilter = new OrFilter();
orFilter.or(new WhitespaceWildcardsFilter(getUserPermissionsAttribute(), name));
if (!CN.equals(getUserPermissionsAttribute())) {
orFilter.or(new WhitespaceWildcardsFilter(CN, name));
}
final AndFilter andFilter2 = new AndFilter();
andFilter2.and(new PresentFilter(getUserPermissionsAttribute()));
andFilter.and(orFilter);
andFilter2.and(andFilter);
LOGGER.debug("Using filter '{}' for LDAP query.", andFilter);
return ldapTemplate.search(
getPermissionSearchBase(),
andFilter2.encode(),
SearchControls.SUBTREE_SCOPE,
getLookUpPermissionAttributesToReturn(),
new PermissionContextMapper());
}
public AccessIdRepresentationModel searchAccessIdByDn(final String dn)
throws InvalidNameException {
isInitOrFail();
// Obviously Spring LdapTemplate does have a inconsistency and always adds the base name to the
// given DN.
// https://stackoverflow.com/questions/55285743/spring-ldaptemplate-how-to-lookup-fully-qualified-dn-with-configured-base-dn
// Therefore we have to remove the base name from the dn before performing the lookup
String nameWithoutBaseDn = getNameWithoutBaseDn(dn).toLowerCase();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Removed baseDN {} from given DN. New DN to be used: {}", getBaseDn(), nameWithoutBaseDn);
}
return ldapTemplate.lookup(
new LdapName(nameWithoutBaseDn),
getLookUpUserAndGroupAndPermissionAttributesToReturn(),
new DnContextMapper());
}
public List searchGroupsAccessIdIsMemberOf(final String accessId)
throws InvalidArgumentException, InvalidNameException {
isInitOrFail();
testMinSearchForLength(accessId);
String dn = searchDnForAccessId(accessId);
if (dn == null || dn.isEmpty()) {
throw new InvalidArgumentException("The AccessId is invalid");
}
final AndFilter andFilter = new AndFilter();
andFilter.and(new EqualsFilter(getGroupSearchFilterName(), getGroupSearchFilterValue()));
final OrFilter orFilter = new OrFilter();
if (!"DN".equalsIgnoreCase(getGroupsOfUserType())) {
orFilter.or(new EqualsFilter(getGroupsOfUserName(), accessId));
}
orFilter.or(new EqualsFilter(getGroupsOfUserName(), dn));
final AndFilter andFilter2 = new AndFilter();
andFilter2.and(new NotPresentFilter(getUserPermissionsAttribute()));
andFilter.and(orFilter);
andFilter2.and(andFilter);
String[] userAttributesToReturn = {getUserIdAttribute(), getGroupNameAttribute()};
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Using filter '{}' for LDAP query with group search base {}.",
andFilter2,
getGroupSearchBase());
}
return ldapTemplate.search(
getGroupSearchBase(),
andFilter2.encode(),
SearchControls.SUBTREE_SCOPE,
userAttributesToReturn,
new GroupContextMapper());
}
public List searchPermissionsAccessIdHas(final String accessId)
throws InvalidArgumentException, InvalidNameException {
isInitOrFail();
testMinSearchForLength(accessId);
String dn = searchDnForAccessId(accessId);
if (dn == null || dn.isEmpty()) {
throw new InvalidArgumentException("The AccessId is invalid");
}
final AndFilter andFilter = new AndFilter();
andFilter.and(new EqualsFilter(getPermissionSearchFilterName(),
getPermissionSearchFilterValue()));
final OrFilter orFilter = new OrFilter();
if (!"DN".equalsIgnoreCase(getPermissionsOfUserType())) {
orFilter.or(new EqualsFilter(getPermissionsOfUserName(), accessId));
}
orFilter.or(new EqualsFilter(getPermissionsOfUserName(), dn));
final AndFilter andFilter2 = new AndFilter();
andFilter2.and(new PresentFilter(getUserPermissionsAttribute()));
andFilter.and(orFilter);
andFilter2.and(andFilter);
String[] userAttributesToReturn = {getUserIdAttribute(), getUserPermissionsAttribute()};
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Using filter '{}' for LDAP query with group search base {}.",
andFilter2,
getPermissionSearchBase());
}
return ldapTemplate.search(
getPermissionSearchBase(),
andFilter2.encode(),
SearchControls.SUBTREE_SCOPE,
userAttributesToReturn,
new PermissionContextMapper());
}
/**
* Performs a lookup to retrieve correct DN for the given access id.
*
* @param accessId The access id to lookup
* @return the LDAP Distinguished Name for the access id
* @throws InvalidArgumentException thrown if the given access id is ambiguous.
* @throws InvalidNameException thrown if name is not a valid dn
*/
public String searchDnForAccessId(String accessId)
throws InvalidArgumentException, InvalidNameException {
isInitOrFail();
if (nameIsDn(accessId)) {
AccessIdRepresentationModel groupByDn = searchAccessIdByDn(accessId);
return groupByDn.getAccessId();
} else {
return searchDnForAccessIdIfAccessIdNameIsNotDn(accessId);
}
}
private String searchDnForAccessIdIfAccessIdNameIsNotDn(String accessId) {
final List distinguishedNames = searchDnForUserAccessId(accessId);
if (distinguishedNames == null || distinguishedNames.isEmpty()) {
final List distinguishedNamesPermissions = searchDnForPermissionAccessId(accessId);
if (distinguishedNamesPermissions == null || distinguishedNamesPermissions.isEmpty()) {
final List distinguishedNamesGroups = searchDnForGroupAccessId(accessId);
if (distinguishedNamesGroups == null || distinguishedNamesGroups.isEmpty()) {
return null;
} else if (distinguishedNamesGroups.size() > 1) {
throw new InvalidArgumentException("Ambiguous access id found: " + accessId);
} else {
return distinguishedNamesGroups.get(0);
}
} else if (distinguishedNamesPermissions.size() > 1) {
throw new InvalidArgumentException("Ambiguous access id found: " + accessId);
} else {
return distinguishedNamesPermissions.get(0);
}
} else if (distinguishedNames.size() > 1) {
throw new InvalidArgumentException("Ambiguous access id found: " + accessId);
} else {
return distinguishedNames.get(0);
}
}
private List searchDnForUserAccessId(String accessId) {
final AndFilter andFilter = new AndFilter();
andFilter.and(new EqualsFilter(getUserSearchFilterName(), getUserSearchFilterValue()));
final OrFilter orFilter = new OrFilter();
orFilter.or(new EqualsFilter(getUserIdAttribute(), accessId));
andFilter.and(orFilter);
LOGGER.debug(
"Using filter '{}' for LDAP query with user search base {}.",
andFilter,
getUserSearchBase());
return ldapTemplate.search(
getUserSearchBase(),
andFilter.encode(),
SearchControls.SUBTREE_SCOPE,
null,
new DnStringContextMapper());
}
private List searchDnForPermissionAccessId(String accessId) {
final AndFilter andFilter = new AndFilter();
andFilter.and(new EqualsFilter(getPermissionSearchFilterName(),
getPermissionSearchFilterValue()));
final OrFilter orFilter = new OrFilter();
orFilter.or(new EqualsFilter(getUserPermissionsAttribute(), accessId));
final AndFilter andFilterPermission2 = new AndFilter();
andFilter.and(new PresentFilter(getUserPermissionsAttribute()));
andFilter.and(orFilter);
andFilterPermission2.and(andFilter);
LOGGER.debug(
"Using filter '{}' for LDAP query with user search base {}.",
andFilterPermission2,
getPermissionSearchBase());
return ldapTemplate.search(
getPermissionSearchBase(),
andFilterPermission2.encode(),
SearchControls.SUBTREE_SCOPE,
null,
new DnStringContextMapper());
}
private List searchDnForGroupAccessId(String accessId) {
final AndFilter andFilter = new AndFilter();
andFilter.and(new EqualsFilter(getGroupSearchFilterName(), getGroupSearchFilterValue()));
final OrFilter orFilter = new OrFilter();
orFilter.or(new EqualsFilter(getGroupNameAttribute(), accessId));
final AndFilter andFilter2 = new AndFilter();
andFilter2.and(new NotPresentFilter(getUserPermissionsAttribute()));
andFilter.and(orFilter);
andFilter2.and(andFilter);
LOGGER.debug(
"Using filter '{}' for LDAP query with user search base {}.",
andFilter2,
getPermissionSearchBase());
return ldapTemplate.search(
getPermissionSearchBase(),
andFilter.encode(),
SearchControls.SUBTREE_SCOPE,
null,
new DnStringContextMapper());
}
/**
* Validates a given AccessId / name.
*
* @param name lookup string for names or groups
* @return whether the given name is valid or not
* @throws InvalidNameException thrown if name is not a valid dn
*/
public boolean validateAccessId(final String name) throws InvalidNameException {
isInitOrFail();
if (nameIsDn(name)) {
AccessIdRepresentationModel groupByDn = searchAccessIdByDn(name);
return groupByDn != null;
} else {
final AndFilter andFilter = new AndFilter();
andFilter.and(new EqualsFilter(getUserSearchFilterName(), getUserSearchFilterValue()));
final OrFilter orFilter = new OrFilter();
orFilter.or(new EqualsFilter(getUserIdAttribute(), name));
andFilter.and(orFilter);
final List accessIds =
ldapTemplate.search(
getUserSearchBase(),
andFilter.encode(),
SearchControls.SUBTREE_SCOPE,
getLookUpUserAttributesToReturn(),
new UserContextMapper());
return !accessIds.isEmpty();
}
}
public String getUserSearchBase() {
return LdapSettings.TASKANA_LDAP_USER_SEARCH_BASE.getValueFromEnv(env);
}
public String getUserSearchFilterName() {
return LdapSettings.TASKANA_LDAP_USER_SEARCH_FILTER_NAME.getValueFromEnv(env);
}
public String getUserSearchFilterValue() {
return LdapSettings.TASKANA_LDAP_USER_SEARCH_FILTER_VALUE.getValueFromEnv(env);
}
public String getUserFirstnameAttribute() {
return LdapSettings.TASKANA_LDAP_USER_FIRSTNAME_ATTRIBUTE.getValueFromEnv(env);
}
public String getUserLastnameAttribute() {
return LdapSettings.TASKANA_LDAP_USER_LASTNAME_ATTRIBUTE.getValueFromEnv(env);
}
public String getUserPhoneAttribute() {
return LdapSettings.TASKANA_LDAP_USER_PHONE_ATTRIBUTE.getValueFromEnv(env);
}
public String getUserMobilePhoneAttribute() {
return LdapSettings.TASKANA_LDAP_USER_MOBILE_PHONE_ATTRIBUTE.getValueFromEnv(env);
}
public String getUserEmailAttribute() {
return LdapSettings.TASKANA_LDAP_USER_EMAIL_ATTRIBUTE.getValueFromEnv(env);
}
public String getUserOrgLevel1Attribute() {
return LdapSettings.TASKANA_LDAP_USER_ORG_LEVEL_1_ATTRIBUTE.getValueFromEnv(env);
}
public String getUserOrgLevel2Attribute() {
return LdapSettings.TASKANA_LDAP_USER_ORG_LEVEL_2_ATTRIBUTE.getValueFromEnv(env);
}
public String getUserOrgLevel3Attribute() {
return LdapSettings.TASKANA_LDAP_USER_ORG_LEVEL_3_ATTRIBUTE.getValueFromEnv(env);
}
public String getUserOrgLevel4Attribute() {
return LdapSettings.TASKANA_LDAP_USER_ORG_LEVEL_4_ATTRIBUTE.getValueFromEnv(env);
}
public String getUserIdAttribute() {
return LdapSettings.TASKANA_LDAP_USER_ID_ATTRIBUTE.getValueFromEnv(env);
}
public String getUserMemberOfGroupAttribute() {
return LdapSettings.TASKANA_LDAP_USER_MEMBER_OF_GROUP_ATTRIBUTE.getValueFromEnv(env);
}
public String getUserPermissionsAttribute() {
return LdapSettings.TASKANA_LDAP_USER_PERMISSIONS_ATTRIBUTE.getValueFromEnv(env);
}
public String getPermissionSearchBase() {
return LdapSettings.TASKANA_LDAP_PERMISSION_SEARCH_BASE.getValueFromEnv(env);
}
public String getPermissionSearchFilterName() {
return LdapSettings.TASKANA_LDAP_PERMISSION_SEARCH_FILTER_NAME.getValueFromEnv(env);
}
public String getPermissionSearchFilterValue() {
return LdapSettings.TASKANA_LDAP_PERMISSION_SEARCH_FILTER_VALUE.getValueFromEnv(env);
}
public String getPermissionNameAttribute() {
return LdapSettings.TASKANA_LDAP_PERMISSION_NAME_ATTRIBUTE.getValueFromEnv(env);
}
public String getGroupSearchBase() {
return LdapSettings.TASKANA_LDAP_GROUP_SEARCH_BASE.getValueFromEnv(env);
}
public String getBaseDn() {
return LdapSettings.TASKANA_LDAP_BASE_DN.getValueFromEnv(env);
}
public String getGroupSearchFilterName() {
return LdapSettings.TASKANA_LDAP_GROUP_SEARCH_FILTER_NAME.getValueFromEnv(env);
}
public String getGroupSearchFilterValue() {
return LdapSettings.TASKANA_LDAP_GROUP_SEARCH_FILTER_VALUE.getValueFromEnv(env);
}
public String getGroupNameAttribute() {
return LdapSettings.TASKANA_LDAP_GROUP_NAME_ATTRIBUTE.getValueFromEnv(env);
}
public int calcMinSearchForLength(int defaultValue) {
String envValue = LdapSettings.TASKANA_LDAP_MIN_SEARCH_FOR_LENGTH.getValueFromEnv(env);
if (envValue == null || envValue.isEmpty()) {
return defaultValue;
}
return Integer.parseInt(envValue);
}
public int getMinSearchForLength() {
return minSearchForLength;
}
public int calcMaxNumberOfReturnedAccessIds(int defaultValue) {
String envValue =
LdapSettings.TASKANA_LDAP_MAX_NUMBER_OF_RETURNED_ACCESS_IDS.getValueFromEnv(env);
if (envValue == null || envValue.isEmpty()) {
return defaultValue;
}
return Integer.parseInt(envValue);
}
public boolean useDnForGroups() {
String envValue =
LdapSettings.TASKANA_LDAP_USE_DN_FOR_GROUPS.getValueFromEnv(env);
if (envValue == null || envValue.isEmpty()) {
return true;
}
return Boolean.parseBoolean(envValue);
}
public int getMaxNumberOfReturnedAccessIds() {
return maxNumberOfReturnedAccessIds;
}
public String getGroupsOfUserName() {
String groupsOfUser = LdapSettings.TASKANA_LDAP_GROUPS_OF_USER_NAME.getValueFromEnv(env);
if (groupsOfUser == null || groupsOfUser.isEmpty()) {
groupsOfUser = LdapSettings.TASKANA_LDAP_GROUPS_OF_USER.getValueFromEnv(env);
}
return groupsOfUser;
}
public String getGroupsOfUserType() {
return LdapSettings.TASKANA_LDAP_GROUPS_OF_USER_TYPE.getValueFromEnv(env);
}
public String getPermissionsOfUserName() {
String permissionsOfUser =
LdapSettings.TASKANA_LDAP_PERMISSIONS_OF_USER_NAME.getValueFromEnv(env);
if (permissionsOfUser == null || permissionsOfUser.isEmpty()) {
permissionsOfUser = LdapSettings.TASKANA_LDAP_PERMISSIONS_OF_USER.getValueFromEnv(env);
}
return permissionsOfUser;
}
public String getPermissionsOfUserType() {
return LdapSettings.TASKANA_LDAP_PERMISSIONS_OF_USER_TYPE.getValueFromEnv(env);
}
public boolean isUser(String accessId) {
return !getUsersByAccessId(accessId).isEmpty();
}
boolean nameIsDn(String name) {
return name.toLowerCase().endsWith(getBaseDn().toLowerCase());
}
List getFirstPageOfaResultList(
List accessIds) {
return accessIds.subList(0, Math.min(accessIds.size(), maxNumberOfReturnedAccessIds));
}
void isInitOrFail() {
if (!active) {
throw new SystemException(
String.format(
"LdapClient was called but is not active due to missing configuration: %s", message));
}
}
/**
* Sorts a list of AccessIds by their accessId, null values last.
*
* IMPORTANT: The passed list has to implement the optional {@link List#sort} operation.
* Otherwise an exception is thrown.
*
* @param accessIds the list which should be sorted
*/
void sortListOfAccessIdResources(List accessIds) {
accessIds.sort(
Comparator.comparing(
AccessIdRepresentationModel::getAccessId,
Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER)));
}
String getNameWithoutBaseDn(String name) {
// (?i) --> case insensitive replacement
return name.replaceAll("(?i)" + Pattern.quote("," + getBaseDn()), "");
}
String[] getLookUpGroupAttributesToReturn() {
if (CN.equals(getGroupNameAttribute())) {
return new String[] {CN, getGroupSearchFilterName()};
}
return new String[] {getGroupNameAttribute(), CN, getGroupSearchFilterName()};
}
String[] getLookUpPermissionAttributesToReturn() {
return new String[] {
getUserPermissionsAttribute(),
getPermissionSearchFilterName()
};
}
String[] getLookUpUserAndGroupAndPermissionAttributesToReturn() {
return Stream.concat(Stream.concat(
Arrays.stream(getLookUpUserAttributesToReturn()),
Arrays.stream(getLookUpGroupAttributesToReturn())),
Arrays.stream(getLookUpPermissionAttributesToReturn())
)
.toArray(String[]::new);
}
String[] getLookUpUserAttributesToReturn() {
return new String[] {
getUserFirstnameAttribute(),
getUserLastnameAttribute(),
getUserIdAttribute(),
getUserSearchFilterName()
};
}
String[] getLookUpUserInfoAttributesToReturn() {
return new String[] {
getUserIdAttribute(),
getUserMemberOfGroupAttribute(),
getUserPermissionsAttribute(),
getUserFirstnameAttribute(),
getUserLastnameAttribute(),
getUserFullnameAttribute(),
getUserPhoneAttribute(),
getUserMobilePhoneAttribute(),
getUserEmailAttribute(),
getUserOrgLevel1Attribute(),
getUserOrgLevel2Attribute(),
getUserOrgLevel3Attribute(),
getUserOrgLevel4Attribute()
};
}
@PostConstruct
void init() {
minSearchForLength = calcMinSearchForLength(3);
maxNumberOfReturnedAccessIds = calcMaxNumberOfReturnedAccessIds(50);
ldapTemplate.setDefaultCountLimit(maxNumberOfReturnedAccessIds);
final List missingConfigurations = checkForMissingConfigurations();
if (!missingConfigurations.isEmpty()) {
message = String.format("LDAP configurations are missing: %s", missingConfigurations);
throw new SystemException(message);
}
active = true;
}
List checkForMissingConfigurations() {
return Arrays.stream(LdapSettings.values())
// optional settings
.filter(not(LdapSettings.TASKANA_LDAP_MAX_NUMBER_OF_RETURNED_ACCESS_IDS::equals))
.filter(not(LdapSettings.TASKANA_LDAP_MIN_SEARCH_FOR_LENGTH::equals))
.filter(not(LdapSettings.TASKANA_LDAP_USER_EMAIL_ATTRIBUTE::equals))
.filter(not(LdapSettings.TASKANA_LDAP_USER_PHONE_ATTRIBUTE::equals))
.filter(not(LdapSettings.TASKANA_LDAP_USER_MOBILE_PHONE_ATTRIBUTE::equals))
.filter(not(LdapSettings.TASKANA_LDAP_USER_ORG_LEVEL_1_ATTRIBUTE::equals))
.filter(not(LdapSettings.TASKANA_LDAP_USER_ORG_LEVEL_2_ATTRIBUTE::equals))
.filter(not(LdapSettings.TASKANA_LDAP_USER_ORG_LEVEL_3_ATTRIBUTE::equals))
.filter(not(LdapSettings.TASKANA_LDAP_USER_ORG_LEVEL_4_ATTRIBUTE::equals))
.filter(not(LdapSettings.TASKANA_LDAP_GROUPS_OF_USER::equals))
.filter(not(LdapSettings.TASKANA_LDAP_GROUPS_OF_USER_NAME::equals))
.filter(not(LdapSettings.TASKANA_LDAP_GROUPS_OF_USER_TYPE::equals))
.filter(not(LdapSettings.TASKANA_LDAP_PERMISSIONS_OF_USER::equals))
.filter(not(LdapSettings.TASKANA_LDAP_PERMISSIONS_OF_USER_NAME::equals))
.filter(not(LdapSettings.TASKANA_LDAP_PERMISSIONS_OF_USER_TYPE::equals))
.filter(p -> p.getValueFromEnv(env) == null)
.toList();
}
void testMinSearchForLength(final String name) throws InvalidArgumentException {
if (name == null || name.length() < minSearchForLength) {
throw new InvalidArgumentException(
String.format(
"search for string %s is too short. Minimum Length is %s",
name, getMinSearchForLength()));
}
}
private String getUserFullnameAttribute() {
return LdapSettings.TASKANA_LDAP_USER_FULLNAME_ATTRIBUTE.getValueFromEnv(env);
}
private String getDnFromContext(final DirContextOperations context) {
String dn = LdapNameBuilder.newInstance(getBaseDn()).add(context.getDn()).build().toString();
if (useLowerCaseForAccessIds) {
return dn.toLowerCase();
} else {
return dn;
}
}
private String getUserIdFromContext(final DirContextOperations context) {
String userId = context.getStringAttribute(getUserIdAttribute());
if (userId != null && useLowerCaseForAccessIds) {
return userId.toLowerCase();
} else {
return userId;
}
}
private String getGroupIdFromContext(final DirContextOperations context) {
String groupId = context.getStringAttribute(getGroupNameAttribute());
if (groupId != null && useLowerCaseForAccessIds) {
return groupId.toLowerCase();
} else {
return groupId;
}
}
private Set getGroupIdsFromContext(final DirContextOperations context) {
String[] groupAttributes = context.getStringAttributes(getUserMemberOfGroupAttribute());
Set groups = groupAttributes != null ? Set.of(groupAttributes) : Collections.emptySet();
if (useLowerCaseForAccessIds) {
groups =
groups.stream()
.filter(Objects::nonNull)
.map(String::toLowerCase)
.collect(Collectors.toSet());
}
return groups;
}
private String getPermissionIdFromContext(final DirContextOperations context) {
String permissionId = context.getStringAttribute(getUserPermissionsAttribute());
if (permissionId != null && useLowerCaseForAccessIds) {
return permissionId.toLowerCase();
} else {
return permissionId;
}
}
private Set getPermissionIdsFromContext(final DirContextOperations context) {
String[] permissionAttributes = context.getStringAttributes(getUserPermissionsAttribute());
Set permissions =
permissionAttributes != null ? Set.of(permissionAttributes) : Collections.emptySet();
if (useLowerCaseForAccessIds) {
permissions =
permissions.stream()
.filter(Objects::nonNull)
.map(String::toLowerCase)
.collect(Collectors.toSet());
}
return permissions;
}
/** Context Mapper for user entries. */
class GroupContextMapper extends AbstractContextMapper {
@Override
public AccessIdRepresentationModel doMapFromContext(final DirContextOperations context) {
final AccessIdRepresentationModel accessId = new AccessIdRepresentationModel();
if (useDnForGroups()) {
accessId.setAccessId(getDnFromContext(context)); // fully qualified dn
} else {
accessId.setAccessId(getGroupIdFromContext(context));
}
accessId.setName(context.getStringAttribute(getGroupNameAttribute()));
return accessId;
}
}
class PermissionContextMapper extends AbstractContextMapper {
@Override
public AccessIdRepresentationModel doMapFromContext(final DirContextOperations context) {
final AccessIdRepresentationModel accessId = new AccessIdRepresentationModel();
accessId.setAccessId(getPermissionIdFromContext(context)); // fully qualified dn
accessId.setName(context.getStringAttribute(getUserPermissionsAttribute()));
return accessId;
}
}
/** Context Mapper for user info entries. */
class UserInfoContextMapper extends AbstractContextMapper {
@Override
public User doMapFromContext(final DirContextOperations context) {
final User user = new UserImpl();
user.setId(getUserIdFromContext(context));
user.setGroups(getGroupIdsFromContext(context));
user.setPermissions(getPermissionIdsFromContext(context));
user.setFirstName(context.getStringAttribute(getUserFirstnameAttribute()));
user.setLastName(context.getStringAttribute(getUserLastnameAttribute()));
user.setFullName(context.getStringAttribute(getUserFullnameAttribute()));
user.setPhone(context.getStringAttribute(getUserPhoneAttribute()));
user.setMobilePhone(context.getStringAttribute(getUserMobilePhoneAttribute()));
user.setEmail(context.getStringAttribute(getUserEmailAttribute()));
user.setOrgLevel1(context.getStringAttribute(getUserOrgLevel1Attribute()));
user.setOrgLevel2(context.getStringAttribute(getUserOrgLevel2Attribute()));
user.setOrgLevel3(context.getStringAttribute(getUserOrgLevel3Attribute()));
user.setOrgLevel4(context.getStringAttribute(getUserOrgLevel4Attribute()));
return user;
}
}
/** Context Mapper for user entries. */
class UserContextMapper extends AbstractContextMapper {
@Override
public AccessIdRepresentationModel doMapFromContext(final DirContextOperations context) {
final AccessIdRepresentationModel accessId = new AccessIdRepresentationModel();
accessId.setAccessId(getUserIdFromContext(context));
String firstName = context.getStringAttribute(getUserFirstnameAttribute());
String lastName = context.getStringAttribute(getUserLastnameAttribute());
accessId.setName(String.format("%s, %s", lastName, firstName));
return accessId;
}
}
/** General Context Mapper for DNs, which can be both, user or groups. */
class DnContextMapper extends AbstractContextMapper {
@Override
public AccessIdRepresentationModel doMapFromContext(final DirContextOperations context) {
final AccessIdRepresentationModel accessId = new AccessIdRepresentationModel();
String[] objectClasses = context.getStringAttributes(getUserSearchFilterName());
if (objectClasses != null
&& Arrays.asList(objectClasses).contains(getUserSearchFilterValue())) {
accessId.setAccessId(getUserIdFromContext(context));
String firstName = context.getStringAttribute(getUserFirstnameAttribute());
String lastName = context.getStringAttribute(getUserLastnameAttribute());
accessId.setName(String.format("%s, %s", lastName, firstName));
} else if (context.getStringAttribute(getUserPermissionsAttribute()) == null) {
if (useDnForGroups()) {
accessId.setAccessId(getDnFromContext(context)); // fully qualified dn
} else {
accessId.setAccessId(getGroupIdFromContext(context));
}
accessId.setName(context.getStringAttribute(getGroupNameAttribute()));
} else {
accessId.setAccessId(getPermissionIdFromContext(context));
accessId.setName(context.getStringAttribute(getUserPermissionsAttribute()));
}
return accessId;
}
}
class DnStringContextMapper extends AbstractContextMapper {
@Override
public String doMapFromContext(DirContextOperations ctx) {
return getDnFromContext(ctx);
}
}
}