From e033f35cdcee2a85c5153888bf9e0369dce18af4 Mon Sep 17 00:00:00 2001 From: Joerg Heffner <56156750+gitgoodjhe@users.noreply.github.com> Date: Tue, 21 Sep 2021 09:23:26 +0200 Subject: [PATCH] TSK-1723: Synchronization of LDAP data for users --- .../src/main/resources/taskana-test.ldif | 7 + .../src/test/resources/application.properties | 7 + .../taskana/TaskanaEngineConfiguration.java | 29 ++++ .../main/resources/application-db2.properties | 7 + .../resources/application-postgres.properties | 7 + .../src/main/resources/application.properties | 7 + .../src/main/resources/taskana.properties | 2 + .../taskana/example/jobs/JobScheduler.java | 2 + .../src/main/resources/taskana-example.ldif | 7 + .../pro/taskana/example/ldap/LdapTest.java | 40 ++++++ .../application-emptySearchRoots.properties | 7 + .../src/test/resources/application.properties | 7 + .../resources/application-postgres.properties | 7 + .../src/test/resources/application.properties | 7 + .../taskana/common/rest/ldap/LdapClient.java | 95 +++++++++++++ .../common/rest/ldap/LdapSettings.java | 7 + .../rest/util/ApplicationContextProvider.java | 19 +++ .../taskana/user/jobs/UserInfoRefreshJob.java | 127 ++++++++++++++++++ .../common/rest/ldap/LdapClientTest.java | 9 +- .../user/jobs/UserInfoRefreshJobIntTest.java | 87 ++++++++++++ .../src/test/resources/application.properties | 7 + .../src/test/resources/taskana.properties | 2 +- 22 files changed, 494 insertions(+), 2 deletions(-) create mode 100644 rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/util/ApplicationContextProvider.java create mode 100644 rest/taskana-rest-spring/src/main/java/pro/taskana/user/jobs/UserInfoRefreshJob.java create mode 100644 rest/taskana-rest-spring/src/test/java/pro/taskana/user/jobs/UserInfoRefreshJobIntTest.java diff --git a/common/taskana-common-test/src/main/resources/taskana-test.ldif b/common/taskana-common-test/src/main/resources/taskana-test.ldif index 2e38eafba..30bd7e809 100644 --- a/common/taskana-common-test/src/main/resources/taskana-test.ldif +++ b/common/taskana-common-test/src/main/resources/taskana-test.ldif @@ -93,6 +93,13 @@ objectclass: person objectclass: top givenName: Titus description: desc +phoneNumber: 012345678 +mobileNumber: 09876554321 +email: Titus.Toll@taskana.de +orgLevel1: ABC +orgLevel2: DEF/GHI +someDepartement: JKL +orgLevel4: MNO/PQR memberOf: cn=Organisationseinheit KSC 1,cn=Organisationseinheit KSC,cn=organisation,OU=Test,O=TASKANA memberOf: cn=monitor-users,cn=groups,OU=Test,O=TASKANA memberOf: cn=business-admins,cn=groups,OU=Test,O=TASKANA 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 5be2766f1..9431b5e72 100644 --- a/history/taskana-simplehistory-rest-spring/src/test/resources/application.properties +++ b/history/taskana-simplehistory-rest-spring/src/test/resources/application.properties @@ -13,6 +13,13 @@ taskana.ldap.userSearchFilterValue=person taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn +taskana.ldap.userPhoneAttribute=phoneNumber +taskana.ldap.userMobilePhoneAttribute=mobileNumber +taskana.ldap.userEmailAttribute=email +taskana.ldap.userOrglevel1Attribute=orgLevel1 +taskana.ldap.userOrglevel2Attribute=orgLevel2 +taskana.ldap.userOrglevel3Attribute=someDepartement +taskana.ldap.userOrglevel4Attribute=orgLevel4 taskana.ldap.userIdAttribute=uid taskana.ldap.userMemberOfGroupAttribute=memberOf taskana.ldap.groupSearchBase=cn=groups diff --git a/lib/taskana-core/src/main/java/pro/taskana/TaskanaEngineConfiguration.java b/lib/taskana-core/src/main/java/pro/taskana/TaskanaEngineConfiguration.java index 41188d2d0..d6683694a 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/TaskanaEngineConfiguration.java +++ b/lib/taskana-core/src/main/java/pro/taskana/TaskanaEngineConfiguration.java @@ -62,7 +62,11 @@ public class TaskanaEngineConfiguration { private static final String TASKANA_JOB_PRIORITY_BATCHSIZE = "taskana.jobs.priority.batchSize"; private static final String TASKANA_JOB_PRIORITY_RUN_EVERY = "taskana.jobs.priority.runEvery"; private static final String TASKANA_JOB_PRIORITY_FIRST_RUN = "taskana.jobs.priority.firstRunAt"; + private static final String TASKANA_JOB_USER_REFRESH_FIRST_RUN = + "taskana.jobs.user.refresh.firstRunAt"; private static final String TASKANA_JOB_PRIORITY_ACTIVE = "taskana.jobs.priority.active"; + private static final String TASKANA_JOB_USER_REFRESH_RUN_EVERY = + "taskana.jobs.user.refresh.runEvery"; private static final String TASKANA_DOMAINS_PROPERTY = "taskana.domains"; private static final String TASKANA_CLASSIFICATION_TYPES_PROPERTY = "taskana.classification.types"; @@ -121,6 +125,9 @@ public class TaskanaEngineConfiguration { private Duration priorityJobRunEvery = Duration.parse("P1D"); private boolean priorityJobActive = false; + private Duration userRefreshJobRunEvery = Duration.parse("P1D"); + private Instant userRefreshJobFirstRun = Instant.parse("2018-01-01T23:00:00Z"); + public TaskanaEngineConfiguration( DataSource dataSource, boolean useManagedTransactions, String schemaName) { this(dataSource, useManagedTransactions, true, schemaName); @@ -419,6 +426,22 @@ public class TaskanaEngineConfiguration { this.priorityJobRunEvery = priorityJobRunEvery; } + public Duration getUserRefreshJobRunEvery() { + return userRefreshJobRunEvery; + } + + public void setUserRefreshJobRunEvery(Duration userRefreshJobRunEvery) { + this.userRefreshJobRunEvery = userRefreshJobRunEvery; + } + + public Instant getUserRefreshJobFirstRun() { + return userRefreshJobFirstRun; + } + + public void setUserRefreshJobFirstRun(Instant userRefreshJobFirstRun) { + this.userRefreshJobFirstRun = userRefreshJobFirstRun; + } + public boolean isPriorityJobActive() { return priorityJobActive; } @@ -495,6 +518,12 @@ public class TaskanaEngineConfiguration { parseProperty(props, TASKANA_JOB_PRIORITY_ACTIVE, Boolean::parseBoolean) .ifPresent(this::setPriorityJobActive); + parseProperty(props, TASKANA_JOB_USER_REFRESH_RUN_EVERY, Duration::parse) + .ifPresent(this::setUserRefreshJobRunEvery); + + parseProperty(props, TASKANA_JOB_USER_REFRESH_FIRST_RUN, Instant::parse) + .ifPresent(this::setUserRefreshJobFirstRun); + parseProperty( props, TASKANA_JOB_TASK_CLEANUP_ALL_COMPLETED_SAME_PARENT_BUSINESS, diff --git a/rest/taskana-rest-spring-example-boot/src/main/resources/application-db2.properties b/rest/taskana-rest-spring-example-boot/src/main/resources/application-db2.properties index 7c0f53266..62a088baa 100644 --- a/rest/taskana-rest-spring-example-boot/src/main/resources/application-db2.properties +++ b/rest/taskana-rest-spring-example-boot/src/main/resources/application-db2.properties @@ -67,6 +67,13 @@ taskana.ldap.userSearchFilterValue=person taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn +taskana.ldap.userPhoneAttribute=phoneNumber +taskana.ldap.userMobilePhoneAttribute=mobileNumber +taskana.ldap.userEmailAttribute=email +taskana.ldap.userOrglevel1Attribute=orgLevel1 +taskana.ldap.userOrglevel2Attribute=orgLevel2 +taskana.ldap.userOrglevel3Attribute=someDepartement +taskana.ldap.userOrglevel4Attribute=orgLevel4 taskana.ldap.userIdAttribute=uid taskana.ldap.groupSearchBase=cn=groups taskana.ldap.groupSearchFilterName=objectclass diff --git a/rest/taskana-rest-spring-example-boot/src/main/resources/application-postgres.properties b/rest/taskana-rest-spring-example-boot/src/main/resources/application-postgres.properties index 232e65401..e1c62d641 100644 --- a/rest/taskana-rest-spring-example-boot/src/main/resources/application-postgres.properties +++ b/rest/taskana-rest-spring-example-boot/src/main/resources/application-postgres.properties @@ -45,6 +45,13 @@ taskana.ldap.userSearchFilterValue=person taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn +taskana.ldap.userPhoneAttribute=phoneNumber +taskana.ldap.userMobilePhoneAttribute=mobileNumber +taskana.ldap.userEmailAttribute=email +taskana.ldap.userOrglevel1Attribute=orgLevel1 +taskana.ldap.userOrglevel2Attribute=orgLevel2 +taskana.ldap.userOrglevel3Attribute=someDepartement +taskana.ldap.userOrglevel4Attribute=orgLevel4 taskana.ldap.userIdAttribute=uid taskana.ldap.groupSearchBase=ou=groups taskana.ldap.groupSearchFilterName=objectclass 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 5b2ba888e..0ae497957 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 @@ -70,6 +70,13 @@ taskana.ldap.userSearchFilterValue=person taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn +taskana.ldap.userPhoneAttribute=phoneNumber +taskana.ldap.userMobilePhoneAttribute=mobileNumber +taskana.ldap.userEmailAttribute=email +taskana.ldap.userOrglevel1Attribute=orgLevel1 +taskana.ldap.userOrglevel2Attribute=orgLevel2 +taskana.ldap.userOrglevel3Attribute=someDepartement +taskana.ldap.userOrglevel4Attribute=orgLevel4 taskana.ldap.userIdAttribute=uid taskana.ldap.userMemberOfGroupAttribute=memberOf taskana.ldap.groupSearchBase= diff --git a/rest/taskana-rest-spring-example-boot/src/main/resources/taskana.properties b/rest/taskana-rest-spring-example-boot/src/main/resources/taskana.properties index bfb187b43..886dc957a 100644 --- a/rest/taskana-rest-spring-example-boot/src/main/resources/taskana.properties +++ b/rest/taskana-rest-spring-example-boot/src/main/resources/taskana.properties @@ -18,6 +18,8 @@ taskana.jobs.history.batchSize=50 taskana.jobs.history.cleanup.firstRunAt=2018-07-25T08:00:00Z taskana.jobs.history.cleanup.minimumAge=P14D taskana.jobs.history.cleanup.runEvery=P1D +taskana.jobs.user.refresh.runEvery=P1D +taskana.jobs.user.refresh.firstRunAt=2018-07-25T23:00:00Z taskana.german.holidays.enabled=true taskana.german.holidays.corpus-christi.enabled=true taskana.historylogger.name=AUDIT diff --git a/rest/taskana-rest-spring-example-common/src/main/java/pro/taskana/example/jobs/JobScheduler.java b/rest/taskana-rest-spring-example-common/src/main/java/pro/taskana/example/jobs/JobScheduler.java index 8a5992a6e..cc77c07bb 100644 --- a/rest/taskana-rest-spring-example-common/src/main/java/pro/taskana/example/jobs/JobScheduler.java +++ b/rest/taskana-rest-spring-example-common/src/main/java/pro/taskana/example/jobs/JobScheduler.java @@ -12,6 +12,7 @@ import pro.taskana.common.api.TaskanaEngine; import pro.taskana.common.internal.jobs.JobRunner; import pro.taskana.common.internal.transaction.TaskanaTransactionProvider; import pro.taskana.task.internal.jobs.TaskCleanupJob; +import pro.taskana.user.jobs.UserInfoRefreshJob; import pro.taskana.workbasket.internal.jobs.WorkbasketCleanupJob; /** This class invokes the JobRunner periodically to schedule long running jobs. */ @@ -35,6 +36,7 @@ public class JobScheduler { ClassNotFoundException { TaskCleanupJob.initializeSchedule(taskanaEngine); WorkbasketCleanupJob.initializeSchedule(taskanaEngine); + UserInfoRefreshJob.initializeSchedule(taskanaEngine); if (taskanaEngine.isHistoryEnabled()) { Thread.currentThread() diff --git a/rest/taskana-rest-spring-example-common/src/main/resources/taskana-example.ldif b/rest/taskana-rest-spring-example-common/src/main/resources/taskana-example.ldif index f296b0c26..962a39cea 100644 --- a/rest/taskana-rest-spring-example-common/src/main/resources/taskana-example.ldif +++ b/rest/taskana-rest-spring-example-common/src/main/resources/taskana-example.ldif @@ -88,6 +88,13 @@ objectclass: person objectclass: top givenName: Titus description: desc +phoneNumber: 012345678 +mobileNumber: 09876554321 +email: Titus.Toll@taskana.de +orgLevel1: QWERT +orgLevel2: DEF/GHI +someDepartement: JKL +orgLevel4: MNO/PQR memberOf: cn=Organisationseinheit KSC 1,cn=Organisationseinheit KSC,cn=organisation,OU=Test,O=TASKANA memberOf: cn=monitor-users,cn=groups,OU=Test,O=TASKANA memberOf: cn=business-admins,cn=groups,OU=Test,O=TASKANA diff --git a/rest/taskana-rest-spring-example-common/src/test/java/pro/taskana/example/ldap/LdapTest.java b/rest/taskana-rest-spring-example-common/src/test/java/pro/taskana/example/ldap/LdapTest.java index 8e00c7b6b..0c3aef911 100644 --- a/rest/taskana-rest-spring-example-common/src/test/java/pro/taskana/example/ldap/LdapTest.java +++ b/rest/taskana-rest-spring-example-common/src/test/java/pro/taskana/example/ldap/LdapTest.java @@ -3,12 +3,16 @@ package pro.taskana.example.ldap; import static org.assertj.core.api.Assertions.assertThat; import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import pro.taskana.common.rest.ldap.LdapClient; import pro.taskana.common.rest.models.AccessIdRepresentationModel; import pro.taskana.common.test.rest.TaskanaSpringBootTest; +import pro.taskana.user.api.models.User; /** Test Ldap attachment. */ @TaskanaSpringBootTest @@ -48,4 +52,40 @@ class LdapTest { String dn = ldapClient.searchDnForAccessId("user-2-2"); assertThat(dn).isEqualTo("uid=user-2-2,cn=users,ou=test,o=taskana"); } + + @Test + void should_ReturnAllUsersInUserRoleWithCorrectAttributes() { + + Map users = + ldapClient.searchUsersInUserRole().stream() + .collect(Collectors.toMap(User::getId, Function.identity())); + + assertThat(users).hasSize(8); + + User teamlead1 = users.get("teamlead-1"); + assertThat(teamlead1.getId()).isEqualTo("teamlead-1"); + assertThat(teamlead1.getFirstName()).isEqualTo("Titus"); + assertThat(teamlead1.getLastName()).isEqualTo("Toll"); + assertThat(teamlead1.getFullName()).isEqualTo("Titus Toll"); + assertThat(teamlead1.getEmail()).isEqualTo("Titus.Toll@taskana.de"); + assertThat(teamlead1.getPhone()).isEqualTo("012345678"); + assertThat(teamlead1.getMobilePhone()).isEqualTo("09876554321"); + assertThat(teamlead1.getOrgLevel1()).isEqualTo("ABC"); + assertThat(teamlead1.getOrgLevel2()).isEqualTo("DEF/GHI"); + assertThat(teamlead1.getOrgLevel3()).isEqualTo("JKL"); + assertThat(teamlead1.getOrgLevel4()).isEqualTo("MNO/PQR"); + + User user11 = users.get("user-1-1"); + assertThat(user11.getId()).isEqualTo("user-1-1"); + assertThat(user11.getFirstName()).isEqualTo("Max"); + assertThat(user11.getLastName()).isEqualTo("Mustermann"); + assertThat(user11.getFullName()).isEqualTo("Max Mustermann"); + assertThat(user11.getEmail()).isNull(); + assertThat(user11.getPhone()).isNull(); + assertThat(user11.getMobilePhone()).isNull(); + assertThat(user11.getOrgLevel1()).isNull(); + assertThat(user11.getOrgLevel2()).isNull(); + assertThat(user11.getOrgLevel3()).isNull(); + assertThat(user11.getOrgLevel4()).isNull(); + } } diff --git a/rest/taskana-rest-spring-example-common/src/test/resources/application-emptySearchRoots.properties b/rest/taskana-rest-spring-example-common/src/test/resources/application-emptySearchRoots.properties index 35477d157..c7b164c0f 100644 --- a/rest/taskana-rest-spring-example-common/src/test/resources/application-emptySearchRoots.properties +++ b/rest/taskana-rest-spring-example-common/src/test/resources/application-emptySearchRoots.properties @@ -21,6 +21,13 @@ taskana.ldap.userSearchFilterValue=person taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn +taskana.ldap.userPhoneAttribute=phoneNumber +taskana.ldap.userMobilePhoneAttribute=mobileNumber +taskana.ldap.userEmailAttribute=email +taskana.ldap.userOrglevel1Attribute=orgLevel1 +taskana.ldap.userOrglevel2Attribute=orgLevel2 +taskana.ldap.userOrglevel3Attribute=someDepartement +taskana.ldap.userOrglevel4Attribute=orgLevel4 taskana.ldap.userIdAttribute=uid taskana.ldap.userMemberOfGroupAttribute=memberOf taskana.ldap.groupSearchBase= 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 d633c819e..e3dd0408d 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 @@ -21,6 +21,13 @@ taskana.ldap.userSearchFilterValue=person taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn +taskana.ldap.userPhoneAttribute=phoneNumber +taskana.ldap.userMobilePhoneAttribute=mobileNumber +taskana.ldap.userEmailAttribute=email +taskana.ldap.userOrglevel1Attribute=orgLevel1 +taskana.ldap.userOrglevel2Attribute=orgLevel2 +taskana.ldap.userOrglevel3Attribute=someDepartement +taskana.ldap.userOrglevel4Attribute=orgLevel4 taskana.ldap.userIdAttribute=uid taskana.ldap.userMemberOfGroupAttribute=memberOf taskana.ldap.groupSearchBase=cn=groups 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 25e534979..b5df91b94 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 @@ -18,6 +18,13 @@ taskana.ldap.userSearchFilterValue=person taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn +taskana.ldap.userPhoneAttribute=phoneNumber +taskana.ldap.userMobilePhoneAttribute=mobileNumber +taskana.ldap.userEmailAttribute=email +taskana.ldap.userOrglevel1Attribute=orgLevel1 +taskana.ldap.userOrglevel2Attribute=orgLevel2 +taskana.ldap.userOrglevel3Attribute=someDepartement +taskana.ldap.userOrglevel4Attribute=orgLevel4 taskana.ldap.userIdAttribute=uid taskana.ldap.userMemberOfGroupAttribute=memberOf taskana.ldap.groupSearchBase= 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 39d875e8d..b6586408b 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 @@ -18,6 +18,13 @@ taskana.ldap.userSearchFilterValue=person taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn +taskana.ldap.userPhoneAttribute=phoneNumber +taskana.ldap.userMobilePhoneAttribute=mobileNumber +taskana.ldap.userEmailAttribute=email +taskana.ldap.userOrglevel1Attribute=orgLevel1 +taskana.ldap.userOrglevel2Attribute=orgLevel2 +taskana.ldap.userOrglevel3Attribute=someDepartement +taskana.ldap.userOrglevel4Attribute=orgLevel4 taskana.ldap.userIdAttribute=uid taskana.ldap.userMemberOfGroupAttribute=memberOf taskana.ldap.groupSearchBase= 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 1e55396f7..a088f227e 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 @@ -29,6 +29,8 @@ 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; +import pro.taskana.user.api.models.User; +import pro.taskana.user.internal.models.UserImpl; /** Class for Ldap access. */ @Component @@ -128,6 +130,33 @@ public class LdapClient { return accessIds; } + public List searchUsersInUserRole() { + + Set userGroupsOrUser = taskanaEngineConfiguration.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 AndFilter andFilter = new AndFilter(); + andFilter.and(userOrGroupFilter); + + final List users = + ldapTemplate.search( + getUserSearchBase(), + andFilter.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(); @@ -349,6 +378,34 @@ public class LdapClient { 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); } @@ -467,6 +524,22 @@ public class LdapClient { }; } + String[] getLookUpUserInfoAttributesToReturn() { + return new String[] { + getUserIdAttribute(), + getUserFirstnameAttribute(), + getUserLastnameAttribute(), + getUserFullnameAttribute(), + getUserPhoneAttribute(), + getUserMobilePhoneAttribute(), + getUserEmailAttribute(), + getUserOrgLevel1Attribute(), + getUserOrgLevel2Attribute(), + getUserOrgLevel3Attribute(), + getUserOrgLevel4Attribute() + }; + } + @PostConstruct void init() { minSearchForLength = calcMinSearchForLength(3); @@ -526,6 +599,28 @@ public class LdapClient { } } + /** Context Mapper for user info entries. */ + class UserInfoContextMapper extends AbstractContextMapper { + + @Override + public User doMapFromContext(final DirContextOperations context) { + final User user = new UserImpl(); + user.setId(context.getStringAttribute(getUserIdAttribute())); + 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 { 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 cc67d1199..f626f236c 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 @@ -10,7 +10,14 @@ enum LdapSettings { TASKANA_LDAP_USER_FIRSTNAME_ATTRIBUTE("taskana.ldap.userFirstnameAttribute"), TASKANA_LDAP_USER_LASTNAME_ATTRIBUTE("taskana.ldap.userLastnameAttribute"), TASKANA_LDAP_USER_FULLNAME_ATTRIBUTE("taskana.ldap.userFullnameAttribute"), + TASKANA_LDAP_USER_PHONE_ATTRIBUTE("taskana.ldap.userPhoneAttribute"), + TASKANA_LDAP_USER_MOBILE_PHONE_ATTRIBUTE("taskana.ldap.userMobilePhoneAttribute"), + TASKANA_LDAP_USER_EMAIL_ATTRIBUTE("taskana.ldap.userEmailAttribute"), TASKANA_LDAP_USER_ID_ATTRIBUTE("taskana.ldap.userIdAttribute"), + TASKANA_LDAP_USER_ORG_LEVEL_1_ATTRIBUTE("taskana.ldap.userOrglevel1Attribute"), + TASKANA_LDAP_USER_ORG_LEVEL_2_ATTRIBUTE("taskana.ldap.userOrglevel2Attribute"), + TASKANA_LDAP_USER_ORG_LEVEL_3_ATTRIBUTE("taskana.ldap.userOrglevel3Attribute"), + TASKANA_LDAP_USER_ORG_LEVEL_4_ATTRIBUTE("taskana.ldap.userOrglevel4Attribute"), 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"), diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/util/ApplicationContextProvider.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/util/ApplicationContextProvider.java new file mode 100644 index 000000000..7ea96b187 --- /dev/null +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/util/ApplicationContextProvider.java @@ -0,0 +1,19 @@ +package pro.taskana.common.rest.util; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +@Component +public class ApplicationContextProvider { + private static ApplicationContext context; + + public static ApplicationContext getApplicationContext() { + return context; + } + + @Autowired + public void setContext(ApplicationContext context) { + ApplicationContextProvider.context = context; + } +} diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/user/jobs/UserInfoRefreshJob.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/user/jobs/UserInfoRefreshJob.java new file mode 100644 index 000000000..a6bed31d9 --- /dev/null +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/user/jobs/UserInfoRefreshJob.java @@ -0,0 +1,127 @@ +package pro.taskana.user.jobs; + +import java.sql.PreparedStatement; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import pro.taskana.common.api.ScheduledJob; +import pro.taskana.common.api.TaskanaEngine; +import pro.taskana.common.api.exceptions.InvalidArgumentException; +import pro.taskana.common.api.exceptions.NotAuthorizedException; +import pro.taskana.common.api.exceptions.SystemException; +import pro.taskana.common.internal.JobServiceImpl; +import pro.taskana.common.internal.jobs.AbstractTaskanaJob; +import pro.taskana.common.internal.transaction.TaskanaTransactionProvider; +import pro.taskana.common.rest.ldap.LdapClient; +import pro.taskana.common.rest.util.ApplicationContextProvider; +import pro.taskana.task.internal.jobs.helper.SqlConnectionRunner; +import pro.taskana.user.api.exceptions.UserAlreadyExistException; +import pro.taskana.user.api.exceptions.UserNotFoundException; +import pro.taskana.user.api.models.User; + +/** Job to refresh all user info after a period of time. */ +public class UserInfoRefreshJob extends AbstractTaskanaJob { + + private static final Logger LOGGER = LoggerFactory.getLogger(UserInfoRefreshJob.class); + private final SqlConnectionRunner sqlConnectionRunner; + + public UserInfoRefreshJob(TaskanaEngine taskanaEngine) { + this(taskanaEngine, null, null); + } + + public UserInfoRefreshJob( + TaskanaEngine taskanaEngine, + TaskanaTransactionProvider txProvider, + ScheduledJob scheduledJob) { + super(taskanaEngine, txProvider, scheduledJob, true); + runEvery = taskanaEngine.getConfiguration().getUserRefreshJobRunEvery(); + firstRun = taskanaEngine.getConfiguration().getUserRefreshJobFirstRun(); + sqlConnectionRunner = new SqlConnectionRunner(taskanaEngine); + } + + /** + * Initializes the {@linkplain UserInfoRefreshJob} schedule.
+ * All scheduled jobs are cancelled/deleted and a new one is scheduled. + * + * @param taskanaEngine the TASKANA engine. + */ + public static void initializeSchedule(TaskanaEngine taskanaEngine) { + JobServiceImpl jobService = (JobServiceImpl) taskanaEngine.getJobService(); + UserInfoRefreshJob job = new UserInfoRefreshJob(taskanaEngine); + jobService.deleteJobs(job.getType()); + job.scheduleNextJob(); + } + + @Override + protected String getType() { + return UserInfoRefreshJob.class.getName(); + } + + @Override + protected void execute() { + LOGGER.info("Running job to refresh all user info"); + + LdapClient ldapClient = + ApplicationContextProvider.getApplicationContext().getBean("ldapClient", LdapClient.class); + + try { + + List users = ldapClient.searchUsersInUserRole(); + addExistingConfigurationDataToUsers(users); + clearExistingUsers(); + insertNewUsers(users); + + LOGGER.info("Job to refresh all user info has finished."); + + } catch (Exception e) { + throw new SystemException("Error while processing UserRefreshJob.", e); + } + } + + private void clearExistingUsers() { + + sqlConnectionRunner.runWithConnection( + connection -> { + String sql = "DELETE FROM USER_INFO"; + PreparedStatement statement = connection.prepareStatement(sql); + statement.execute(); + + if (!connection.getAutoCommit()) { + connection.commit(); + } + }); + } + + private void insertNewUsers(List users) { + + users.forEach( + user -> { + try { + taskanaEngineImpl.getUserService().createUser(user); + } catch (InvalidArgumentException + | NotAuthorizedException + | UserAlreadyExistException e) { + throw new SystemException("Caught Exception while trying to insert new User", e); + } + }); + } + + private void addExistingConfigurationDataToUsers(List users) { + + users.forEach( + user -> { + try { + user.setData(taskanaEngineImpl.getUserService().getUser(user.getId()).getData()); + } catch (UserNotFoundException e) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + String.format( + "Failed to fetch configuration data for User " + + "with ID '%s' because it doesn't exist"), + user.getId()); + } + } + }); + } +} 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 5be5ed532..63c3de9f1 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 @@ -177,7 +177,14 @@ class LdapClientTest { {"taskana.ldap.userLastnameAttribute", "sn"}, {"taskana.ldap.userFirstnameAttribute", "givenName"}, {"taskana.ldap.userFullnameAttribute", "cn"}, - {"taskana.ldap.userSearchFilterValue", "person"} + {"taskana.ldap.userSearchFilterValue", "person"}, + {"taskana.ldap.userPhoneAttribute", "phoneNumber"}, + {"taskana.ldap.userMobilePhoneAttribute", "mobileNumber"}, + {"taskana.ldap.userEmailAttribute", "email"}, + {"taskana.ldap.userOrglevel1Attribute", "orgLevel1"}, + {"taskana.ldap.userOrglevel2Attribute", "orgLevel2"}, + {"taskana.ldap.userOrglevel3Attribute", "orgLevel3"}, + {"taskana.ldap.userOrglevel4Attribute", "orgLevel4"}, }) .forEach( strings -> diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/user/jobs/UserInfoRefreshJobIntTest.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/user/jobs/UserInfoRefreshJobIntTest.java new file mode 100644 index 000000000..6c015124a --- /dev/null +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/user/jobs/UserInfoRefreshJobIntTest.java @@ -0,0 +1,87 @@ +package pro.taskana.user.jobs; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; + +import pro.taskana.common.api.TaskanaEngine; +import pro.taskana.common.rest.ldap.LdapClient; +import pro.taskana.common.test.rest.TaskanaSpringBootTest; +import pro.taskana.common.test.security.JaasExtension; +import pro.taskana.common.test.security.WithAccessId; +import pro.taskana.user.api.UserService; +import pro.taskana.user.api.models.User; +import pro.taskana.user.internal.models.UserImpl; + +@TaskanaSpringBootTest +@ExtendWith(JaasExtension.class) +class UserInfoRefreshJobIntTest { + + TaskanaEngine taskanaEngine; + UserService userService; + LdapClient ldapClient; + + @Autowired + public UserInfoRefreshJobIntTest( + TaskanaEngine taskanaEngine, UserService userService, LdapClient ldapClient) { + this.taskanaEngine = taskanaEngine; + this.userService = userService; + this.ldapClient = ldapClient; + } + + @Test + @WithAccessId(user = "businessadmin") + void should_RefreshUserInfo_When_UserInfoRefreshJobIsExecuted() throws Exception { + + try (Connection connection = taskanaEngine.getConfiguration().getDatasource().getConnection()) { + + List users = getUsers(connection); + assertThat(users).hasSize(14); + + UserInfoRefreshJob userInfoRefreshJob = new UserInfoRefreshJob(taskanaEngine); + userInfoRefreshJob.execute(); + + users = getUsers(connection); + List ldapusers = ldapClient.searchUsersInUserRole(); + assertThat(users).hasSize(6).hasSameSizeAs(ldapusers); + assertThat(users) + .usingElementComparatorIgnoringFields("longName", "data") + .containsExactlyElementsOf(ldapusers); + } + } + + private List getUsers(Connection connection) throws Exception { + + List users = new ArrayList<>(); + Statement statement = connection.createStatement(); + ResultSet rs = statement.executeQuery("SELECT * FROM " + connection.getSchema() + ".USER_INFO"); + + while (rs.next()) { + User ldapUser = new UserImpl(); + ldapUser.setId(rs.getString("USER_ID")); + ldapUser.setFirstName(rs.getString("FIRST_NAME")); + ldapUser.setLastName(rs.getString("LASTNAME")); + ldapUser.setFullName(rs.getString("FULL_NAME")); + ldapUser.setLongName(rs.getString("LONG_NAME")); + ldapUser.setEmail(rs.getString("E_MAIL")); + ldapUser.setPhone(rs.getString("PHONE")); + ldapUser.setMobilePhone(rs.getString("MOBILE_PHONE")); + ldapUser.setOrgLevel4(rs.getString("ORG_LEVEL_4")); + ldapUser.setOrgLevel3(rs.getString("ORG_LEVEL_3")); + ldapUser.setOrgLevel2(rs.getString("ORG_LEVEL_2")); + ldapUser.setOrgLevel1(rs.getString("ORG_LEVEL_1")); + ldapUser.setData(rs.getString("DATA")); + + users.add(ldapUser); + } + + return users; + } +} diff --git a/rest/taskana-rest-spring/src/test/resources/application.properties b/rest/taskana-rest-spring/src/test/resources/application.properties index 9b9775612..76605096c 100644 --- a/rest/taskana-rest-spring/src/test/resources/application.properties +++ b/rest/taskana-rest-spring/src/test/resources/application.properties @@ -22,6 +22,13 @@ taskana.ldap.userSearchFilterValue=person taskana.ldap.userFirstnameAttribute=givenName taskana.ldap.userLastnameAttribute=sn taskana.ldap.userFullnameAttribute=cn +taskana.ldap.userPhoneAttribute=phoneNumber +taskana.ldap.userMobilePhoneAttribute=mobileNumber +taskana.ldap.userEmailAttribute=email +taskana.ldap.userOrglevel1Attribute=orgLevel1 +taskana.ldap.userOrglevel2Attribute=orgLevel2 +taskana.ldap.userOrglevel3Attribute=someDepartement +taskana.ldap.userOrglevel4Attribute=orgLevel4 taskana.ldap.userIdAttribute=uid taskana.ldap.userMemberOfGroupAttribute=memberOf taskana.ldap.groupSearchBase= diff --git a/rest/taskana-rest-spring/src/test/resources/taskana.properties b/rest/taskana-rest-spring/src/test/resources/taskana.properties index d7a69d8cf..9b77d75de 100644 --- a/rest/taskana-rest-spring/src/test/resources/taskana.properties +++ b/rest/taskana-rest-spring/src/test/resources/taskana.properties @@ -1,4 +1,4 @@ -taskana.roles.user=cn=ksc-users,cn=groups,OU=Test,O=TASKANA | teamlead-1 | teamlead-2 | user-1-1 | user-1-2 | user-2-1 | user-2-2 | user-b-1 | user-b-2 +taskana.roles.user=cn=ksc-users,cn=groups,OU=Test,O=TASKANA | teamlead-1 | teamlead-2 | user-1-1 taskana.roles.admin=admin | uid=admin,cn=users,OU=Test,O=TASKANA taskana.roles.businessadmin=businessadmin | cn=business-admins,cn=groups,OU=Test,O=TASKANA taskana.roles.monitor=monitor | cn=monitor-users,cn=groups,OU=Test,O=TASKANA