diff --git a/history/taskana-simplehistory-rest-spring/src/test/resources/application.properties b/history/taskana-simplehistory-rest-spring/src/test/resources/application.properties index 77851d23a..00aad81ff 100644 --- a/history/taskana-simplehistory-rest-spring/src/test/resources/application.properties +++ b/history/taskana-simplehistory-rest-spring/src/test/resources/application.properties @@ -14,6 +14,7 @@ taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn taskana.ldap.userIdAttribute=uid +taskana.ldap.userMemberOfGroupAttribute= taskana.ldap.groupSearchBase=cn=groups taskana.ldap.groupSearchFilterName=objectclass taskana.ldap.groupSearchFilterValue=groupOfUniqueNames diff --git a/rest/taskana-rest-spring-example-boot/src/main/resources/application.properties b/rest/taskana-rest-spring-example-boot/src/main/resources/application.properties index 48e8eacbe..48d34afcd 100644 --- a/rest/taskana-rest-spring-example-boot/src/main/resources/application.properties +++ b/rest/taskana-rest-spring-example-boot/src/main/resources/application.properties @@ -68,6 +68,7 @@ taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn taskana.ldap.userIdAttribute=uid +taskana.ldap.userMemberOfGroupAttribute= taskana.ldap.groupSearchBase= taskana.ldap.groupSearchFilterName=objectclass taskana.ldap.groupSearchFilterValue=groupofuniquenames diff --git a/rest/taskana-rest-spring-example-common/src/test/resources/application.properties b/rest/taskana-rest-spring-example-common/src/test/resources/application.properties index 59ba71428..5d83da52a 100644 --- a/rest/taskana-rest-spring-example-common/src/test/resources/application.properties +++ b/rest/taskana-rest-spring-example-common/src/test/resources/application.properties @@ -24,6 +24,7 @@ taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn taskana.ldap.userIdAttribute=uid +taskana.ldap.userMemberOfGroupAttribute= taskana.ldap.groupSearchBase=cn=groups taskana.ldap.groupSearchFilterName=objectclass taskana.ldap.groupSearchFilterValue=groupOfUniqueNames diff --git a/rest/taskana-rest-spring-example-wildfly/src/test/resources/application-postgres.properties b/rest/taskana-rest-spring-example-wildfly/src/test/resources/application-postgres.properties index 727f1a4ad..4d335e85f 100644 --- a/rest/taskana-rest-spring-example-wildfly/src/test/resources/application-postgres.properties +++ b/rest/taskana-rest-spring-example-wildfly/src/test/resources/application-postgres.properties @@ -19,6 +19,7 @@ taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn taskana.ldap.userIdAttribute=uid +taskana.ldap.userMemberOfGroupAttribute= taskana.ldap.groupSearchBase= taskana.ldap.groupSearchFilterName=objectclass taskana.ldap.groupSearchFilterValue=groupOfUniqueNames diff --git a/rest/taskana-rest-spring-example-wildfly/src/test/resources/application.properties b/rest/taskana-rest-spring-example-wildfly/src/test/resources/application.properties index 3e9255195..38605e98f 100644 --- a/rest/taskana-rest-spring-example-wildfly/src/test/resources/application.properties +++ b/rest/taskana-rest-spring-example-wildfly/src/test/resources/application.properties @@ -19,6 +19,7 @@ taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn taskana.ldap.userIdAttribute=uid +taskana.ldap.userMemberOfGroupAttribute= taskana.ldap.groupSearchBase= taskana.ldap.groupSearchFilterName=objectclass taskana.ldap.groupSearchFilterValue=groupOfUniqueNames diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/AccessIdController.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/AccessIdController.java index 15f30c5b7..b186636b8 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/AccessIdController.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/AccessIdController.java @@ -62,6 +62,47 @@ public class AccessIdController { return response; } + /** + * This endpoint searches users for a provided name or Access Id. It will only search and return + * users and members of groups which are configured with the requested TASKANA role. This + * search will only work if the users in the configured LDAP have an attribute that shows their + * group memberships, e.g. "memberOf" + * + * @title Search for Access Id (users) in TASKANA user role + * @param nameOrAccessId the name or Access Id which should be searched for. + * @param role the role for which all users should be searched for + * @return a list of all found Access Ids (users) + * @throws InvalidArgumentException if the provided search for Access Id is shorter than the + * configured one. + */ + @GetMapping(path = RestEndpoints.URL_USER) + public ResponseEntity> searchUsersByNameOrAccessIdForRole( + @RequestParam("search-for") String nameOrAccessId, @RequestParam("role") String role) + throws InvalidArgumentException { + + LOGGER.debug( + "Entry to searchUsersByNameOrAccessIdForRole(search-for= {}, role= {})", + nameOrAccessId, + role); + + if (role.equals("user")) { + List accessIdUsers = + ldapClient.searchUsersByNameOrAccessIdInUserRole(nameOrAccessId); + ResponseEntity> response = ResponseEntity.ok(accessIdUsers); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "Exit from searchUsersByNameOrAccessIdForRole(), returning {}", response); + } + + return response; + } else { + throw new InvalidArgumentException( + String.format( + "Requested users for not supported role %s. Only role 'user' is supported'", role)); + } + } + /** * This endpoint retrieves all groups a given Access Id belongs to. * diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/RestEndpoints.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/RestEndpoints.java index 368b046f1..dcff5e084 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/RestEndpoints.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/RestEndpoints.java @@ -17,6 +17,7 @@ public final class RestEndpoints { // access id endpoints public static final String URL_ACCESS_ID = API_V1 + "access-ids"; + public static final String URL_USER = API_V1 + "users"; public static final String URL_ACCESS_ID_GROUPS = API_V1 + "access-ids/groups"; // import / export endpoints diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/ldap/LdapClient.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/ldap/LdapClient.java index f74cb4846..f66c313e3 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/ldap/LdapClient.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/ldap/LdapClient.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -23,6 +24,8 @@ import org.springframework.ldap.filter.WhitespaceWildcardsFilter; import org.springframework.ldap.support.LdapNameBuilder; import org.springframework.stereotype.Component; +import pro.taskana.TaskanaEngineConfiguration; +import pro.taskana.common.api.TaskanaRole; import pro.taskana.common.api.exceptions.InvalidArgumentException; import pro.taskana.common.api.exceptions.SystemException; import pro.taskana.common.rest.models.AccessIdRepresentationModel; @@ -34,6 +37,7 @@ public class LdapClient { private static final Logger LOGGER = LoggerFactory.getLogger(LdapClient.class); private static final String CN = "cn"; + private final TaskanaEngineConfiguration taskanaEngineConfiguration; private final Environment env; private final LdapTemplate ldapTemplate; private boolean active = false; @@ -42,9 +46,13 @@ public class LdapClient { private String message; @Autowired - public LdapClient(Environment env, LdapTemplate ldapTemplate) { + public LdapClient( + Environment env, + LdapTemplate ldapTemplate, + TaskanaEngineConfiguration taskanaEngineConfiguration) { this.env = env; this.ldapTemplate = ldapTemplate; + this.taskanaEngineConfiguration = taskanaEngineConfiguration; } /** @@ -83,6 +91,49 @@ public class LdapClient { return result; } + public List searchUsersByNameOrAccessIdInUserRole( + final String nameOrAccessId) throws InvalidArgumentException { + + LOGGER.debug( + "entry to searchUsersByNameOrAccessIdInUserRoleGroups(nameOrAccessId = {}).", + 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 = taskanaEngineConfiguration.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); + + 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 searchUsersByNameOrAccessId(final String name) throws InvalidArgumentException { LOGGER.debug("entry to searchUsersByNameOrAccessId(name = {}).", name); @@ -278,6 +329,10 @@ public class LdapClient { 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 getGroupSearchBase() { return LdapSettings.TASKANA_LDAP_GROUP_SEARCH_BASE.getValueFromEnv(env); } diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/ldap/LdapSettings.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/ldap/LdapSettings.java index 4a53334e8..cc67d1199 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/ldap/LdapSettings.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/ldap/LdapSettings.java @@ -11,6 +11,7 @@ enum LdapSettings { TASKANA_LDAP_USER_LASTNAME_ATTRIBUTE("taskana.ldap.userLastnameAttribute"), TASKANA_LDAP_USER_FULLNAME_ATTRIBUTE("taskana.ldap.userFullnameAttribute"), TASKANA_LDAP_USER_ID_ATTRIBUTE("taskana.ldap.userIdAttribute"), + TASKANA_LDAP_USER_MEMBER_OF_GROUP_ATTRIBUTE("taskana.ldap.userMemberOfGroupAttribute"), TASKANA_LDAP_GROUP_SEARCH_BASE("taskana.ldap.groupSearchBase"), TASKANA_LDAP_BASE_DN("taskana.ldap.baseDn"), TASKANA_LDAP_GROUP_SEARCH_FILTER_NAME("taskana.ldap.groupSearchFilterName"), diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/common/rest/ldap/LdapClientTest.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/common/rest/ldap/LdapClientTest.java index 227618910..58a659383 100644 --- a/rest/taskana-rest-spring/src/test/java/pro/taskana/common/rest/ldap/LdapClientTest.java +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/common/rest/ldap/LdapClientTest.java @@ -11,7 +11,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -23,6 +26,8 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.env.Environment; import org.springframework.ldap.core.LdapTemplate; +import pro.taskana.TaskanaEngineConfiguration; +import pro.taskana.common.api.TaskanaRole; import pro.taskana.common.api.exceptions.SystemException; import pro.taskana.common.rest.models.AccessIdRepresentationModel; @@ -33,6 +38,8 @@ class LdapClientTest { @Mock LdapTemplate ldapTemplate; + @Mock TaskanaEngineConfiguration taskanaEngineConfiguration; + @InjectMocks LdapClient cut; @Test @@ -90,6 +97,27 @@ class LdapClientTest { assertThat(accessIds.get(3).getAccessId()).isNull(); } + @Test + void should_ReturnAllUsersAndMembersOfGroupsWithTaskanaUserRole() throws Exception { + + setUpEnvMock(); + cut.init(); + + AccessIdRepresentationModel user = new AccessIdRepresentationModel("testU", "testUId"); + + Set groupsOfUserRole = new HashSet<>(); + Map> roleMap = new HashMap<>(); + roleMap.put(TaskanaRole.USER, groupsOfUserRole); + + when(taskanaEngineConfiguration.getRoleMap()).thenReturn(roleMap); + + when(ldapTemplate.search( + any(String.class), any(), anyInt(), any(), any(LdapClient.UserContextMapper.class))) + .thenReturn(List.of(user)); + + assertThat(cut.searchUsersByNameOrAccessIdInUserRole("test")).hasSize(1).containsExactly(user); + } + @Test void testLdap_getNameWithoutBaseDn() { @@ -159,6 +187,7 @@ class LdapClientTest { {"taskana.ldap.groupSearchFilterName", "objectclass"}, {"taskana.ldap.groupSearchBase", "ou=groups"}, {"taskana.ldap.userIdAttribute", "uid"}, + {"taskana.ldap.userMemberOfGroupAttribute", "memberOf"}, {"taskana.ldap.userLastnameAttribute", "sn"}, {"taskana.ldap.userFirstnameAttribute", "givenName"}, {"taskana.ldap.userFullnameAttribute", "cn"}, diff --git a/rest/taskana-rest-spring/src/test/resources/application.properties b/rest/taskana-rest-spring/src/test/resources/application.properties index e09cfb417..08f95a9b2 100644 --- a/rest/taskana-rest-spring/src/test/resources/application.properties +++ b/rest/taskana-rest-spring/src/test/resources/application.properties @@ -23,6 +23,7 @@ taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn taskana.ldap.userIdAttribute=uid +taskana.ldap.userMemberOfGroupAttribute= taskana.ldap.groupSearchBase= taskana.ldap.groupSearchFilterName=objectclass taskana.ldap.groupSearchFilterValue=groupOfUniqueNames