diff --git a/rest/taskana-rest-spring-example-wildfly/src/main/java/pro/taskana/example/wildfly/AdditionalUserProperties.java b/rest/taskana-rest-spring-example-wildfly/src/main/java/pro/taskana/example/wildfly/AdditionalUserProperties.java new file mode 100644 index 000000000..768636e4f --- /dev/null +++ b/rest/taskana-rest-spring-example-wildfly/src/main/java/pro/taskana/example/wildfly/AdditionalUserProperties.java @@ -0,0 +1,24 @@ +package pro.taskana.example.wildfly; + +import java.util.List; + +public class AdditionalUserProperties { + private Boolean enableUserIdHeader; + private List authorizedUsers; + + public Boolean getEnableUserIdHeader() { + return enableUserIdHeader; + } + + public void setEnableUserIdHeader(Boolean enableUserIdHeader) { + this.enableUserIdHeader = enableUserIdHeader; + } + + public List getAuthorizedUsers() { + return authorizedUsers; + } + + public void setAuthorizedUsers(List authorizedUsers) { + this.authorizedUsers = authorizedUsers; + } +} diff --git a/rest/taskana-rest-spring-example-wildfly/src/main/java/pro/taskana/example/wildfly/TaskanaWildflyConfiguration.java b/rest/taskana-rest-spring-example-wildfly/src/main/java/pro/taskana/example/wildfly/TaskanaWildflyConfiguration.java index 7048953e5..f25f14994 100644 --- a/rest/taskana-rest-spring-example-wildfly/src/main/java/pro/taskana/example/wildfly/TaskanaWildflyConfiguration.java +++ b/rest/taskana-rest-spring-example-wildfly/src/main/java/pro/taskana/example/wildfly/TaskanaWildflyConfiguration.java @@ -1,6 +1,7 @@ package pro.taskana.example.wildfly; import java.io.InputStream; +import java.util.List; import java.util.Properties; import javax.naming.Context; import javax.naming.InitialContext; @@ -11,6 +12,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; +import org.springframework.core.env.Environment; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import pro.taskana.sampledata.SampleDataGenerator; @@ -61,4 +63,12 @@ public class TaskanaWildflyConfiguration { throw e; } } + + @Bean + public AdditionalUserProperties getAdditionalUserProperties(Environment env) { + AdditionalUserProperties properties = new AdditionalUserProperties(); + properties.setAuthorizedUsers(List.of(env.getProperty("authorizedUsers", "").split("\\|"))); + properties.setEnableUserIdHeader(env.getProperty("enableUserIdHeader", Boolean.TYPE, false)); + return properties; + } } diff --git a/rest/taskana-rest-spring-example-wildfly/src/main/java/pro/taskana/example/wildfly/security/ElytronToJaasFilter.java b/rest/taskana-rest-spring-example-wildfly/src/main/java/pro/taskana/example/wildfly/security/ElytronToJaasFilter.java index 49f557c62..7ad72832b 100644 --- a/rest/taskana-rest-spring-example-wildfly/src/main/java/pro/taskana/example/wildfly/security/ElytronToJaasFilter.java +++ b/rest/taskana-rest-spring-example-wildfly/src/main/java/pro/taskana/example/wildfly/security/ElytronToJaasFilter.java @@ -2,24 +2,37 @@ package pro.taskana.example.wildfly.security; import java.io.IOException; import java.security.AccessController; +import java.util.List; import javax.security.auth.Subject; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import org.springframework.web.filter.GenericFilterBean; import org.wildfly.security.auth.server.SecurityDomain; import org.wildfly.security.auth.server.SecurityIdentity; import org.wildfly.security.authz.Roles; import pro.taskana.common.api.security.GroupPrincipal; +import pro.taskana.example.wildfly.AdditionalUserProperties; /** Simple Filter to map all Elytron Roles to JAAS-Principals. */ +@Component public class ElytronToJaasFilter extends GenericFilterBean { + private static AdditionalUserProperties additionalUserProperties; + + @Autowired + public void setAdditionalUserProperties(AdditionalUserProperties prop) { + additionalUserProperties = prop; + } + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - SecurityIdentity securityIdentity = getSecurityIdentity(); + SecurityIdentity securityIdentity = getSecurityIdentity(request); if (securityIdentity != null) { applySecurityIdentityToSubject(securityIdentity); } @@ -50,11 +63,24 @@ public class ElytronToJaasFilter extends GenericFilterBean { return subject; } - private SecurityIdentity getSecurityIdentity() { + private SecurityIdentity getSecurityIdentity(ServletRequest request) { SecurityDomain current = SecurityDomain.getCurrent(); SecurityIdentity identity = null; if (current != null) { - identity = current.getCurrentSecurityIdentity(); + if (request instanceof HttpServletRequest) { + HttpServletRequest httpRequest = (HttpServletRequest) request; + String userId = httpRequest.getHeader("userid"); + Boolean enableUserIdHeader = additionalUserProperties.getEnableUserIdHeader(); + List authorizedUsers = additionalUserProperties.getAuthorizedUsers(); + if (userId != null + && enableUserIdHeader + && authorizedUsers.contains( + current.getCurrentSecurityIdentity().getPrincipal().getName())) { + identity = current.getCurrentSecurityIdentity().createRunAsIdentity(userId, false); + } else { + identity = current.getCurrentSecurityIdentity(); + } + } } if (logger.isDebugEnabled()) { logger.debug("Current Elytron SecurityIdentity: " + identity); diff --git a/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyTest.java b/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyTest.java index 2f52708f2..ea282f9e6 100644 --- a/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyTest.java +++ b/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyTest.java @@ -28,8 +28,8 @@ import pro.taskana.common.test.rest.RestHelper; import pro.taskana.task.rest.models.TaskRepresentationModel; /** - * This test class is configured to run with postgres DB if you want to run it with h2 it is needed. - * to change data source configuration at project-defaults.yml. + * This test class is configured to run with postgres DB. In order to run them on db2, change the + * datasource.jndi to java:jboss/datasources/ExampleDS and set taskana.schemaName to TASKANA */ @RunWith(Arquillian.class) public class TaskanaWildflyTest extends AbstractAccTest { diff --git a/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithHistoryLoggerEnabledTest.java b/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithHistoryLoggerEnabledTest.java index 29976f39e..791db072b 100644 --- a/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithHistoryLoggerEnabledTest.java +++ b/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithHistoryLoggerEnabledTest.java @@ -22,8 +22,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This test class is configured to run with postgres DB if you want to run it with h2 it is needed. - * to change data source configuration at project-defaults.yml. + * This test class is configured to run with postgres DB. In order to run them on db2, change the + * datasource.jndi to java:jboss/datasources/ExampleDS and set taskana.schemaName to TASKANA */ @RunWith(Arquillian.class) public class TaskanaWildflyWithHistoryLoggerEnabledTest extends AbstractAccTest { diff --git a/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithSimpleHistoryAndHistoryLoggerEnabledTest.java b/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithSimpleHistoryAndHistoryLoggerEnabledTest.java index 381ff4322..ac2e08ef2 100644 --- a/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithSimpleHistoryAndHistoryLoggerEnabledTest.java +++ b/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithSimpleHistoryAndHistoryLoggerEnabledTest.java @@ -27,8 +27,8 @@ import pro.taskana.simplehistory.rest.models.TaskHistoryEventPagedRepresentation import pro.taskana.task.rest.models.TaskRepresentationModel; /** - * This test class is configured to run with postgres DB if you want to run it with h2 it is needed. - * to change data source configuration at project-defaults.yml. + * This test class is configured to run with postgres DB. In order to run them on db2, change the + * datasource.jndi to java:jboss/datasources/ExampleDS and set taskana.schemaName to TASKANA */ @RunWith(Arquillian.class) public class TaskanaWildflyWithSimpleHistoryAndHistoryLoggerEnabledTest extends AbstractAccTest { diff --git a/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithSimpleHistoryEnabledTest.java b/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithSimpleHistoryEnabledTest.java index 497337529..1224ef318 100644 --- a/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithSimpleHistoryEnabledTest.java +++ b/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithSimpleHistoryEnabledTest.java @@ -27,8 +27,8 @@ import pro.taskana.simplehistory.rest.models.TaskHistoryEventPagedRepresentation import pro.taskana.task.rest.models.TaskRepresentationModel; /** - * This test class is configured to run with postgres DB if you want to run it with h2 it is needed. - * to change data source configuration at project-defaults.yml. + * This test class is configured to run with postgres DB. In order to run them on db2, change the + * datasource.jndi to java:jboss/datasources/ExampleDS and set taskana.schemaName to TASKANA */ @RunWith(Arquillian.class) public class TaskanaWildflyWithSimpleHistoryEnabledTest extends AbstractAccTest { diff --git a/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithUserConfigTest.java b/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithUserConfigTest.java new file mode 100644 index 000000000..44efb8e26 --- /dev/null +++ b/rest/taskana-rest-spring-example-wildfly/src/test/java/pro/taskana/example/wildfly/TaskanaWildflyWithUserConfigTest.java @@ -0,0 +1,170 @@ +package pro.taskana.example.wildfly; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static pro.taskana.common.test.rest.RestHelper.TEMPLATE; + +import java.io.File; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.shrinkwrap.resolver.api.maven.Maven; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.HttpStatusCodeException; +import pro.taskana.common.rest.RestEndpoints; +import pro.taskana.common.rest.models.TaskanaUserInfoRepresentationModel; +import pro.taskana.common.test.rest.RestHelper; +import pro.taskana.task.rest.models.TaskRepresentationModel; + +/** + * This test class is configured to run with postgres DB. In order to run them on db2, change the + * datasource.jndi to java:jboss/datasources/ExampleDS and set taskana.schemaName to TASKANA + */ +@RunWith(Arquillian.class) +public class TaskanaWildflyWithUserConfigTest extends AbstractAccTest { + + private static final Logger LOGGER = + LoggerFactory.getLogger(TaskanaWildflyWithUserConfigTest.class); + + @Deployment(testable = false) + public static Archive createTestArchive() { + + String applicationPropertyFile = "application-with-additional-user-config.properties"; + String dbType = System.getProperty("db.type"); + if (dbType != null && !dbType.isEmpty()) { + applicationPropertyFile = "application-" + dbType + ".properties"; + } + + LOGGER.info( + "Running with db.type '{}' and using property file '{}'", dbType, applicationPropertyFile); + + File[] files = + Maven.resolver() + .loadPomFromFile("pom.xml") + .importRuntimeDependencies() + .resolve() + .withTransitivity() + .asFile(); + + return ShrinkWrap.create(WebArchive.class, "taskana.war") + .addPackages(true, "pro.taskana") + .addAsResource("taskana.properties") + .addAsResource(applicationPropertyFile, "application.properties") + .addAsResource("taskana-test.ldif") + .addAsWebInfResource("int-test-web.xml", "web.xml") + .addAsWebInfResource("int-test-jboss-web.xml", "jboss-web.xml") + .addAsLibraries(files); + } + + @Test + @RunAsClient + public void should_ReturnUserInformation_WhenRelevantUserInUseridHeader() { + HttpHeaders headers = RestHelper.generateHeadersForUser("user-1-1"); + headers.add("userid", "teamlead-1"); + ResponseEntity response = + TEMPLATE.exchange( + restHelper.toUrl("/taskana" + RestEndpoints.URL_CURRENT_USER), + HttpMethod.GET, + new HttpEntity<>(headers), + ParameterizedTypeReference.forType(TaskanaUserInfoRepresentationModel.class)); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + TaskanaUserInfoRepresentationModel currentUser = response.getBody(); + assertThat(currentUser).isNotNull(); + assertThat(currentUser.getUserId()).isEqualTo("teamlead-1"); + assertThat(currentUser.getGroupIds()).hasSize(4); + assertThat(currentUser.getRoles()).hasSize(3); + } + + @Test + @RunAsClient + public void should_ReturnUserInformation_WhenCurrentUserNotAuthorizedToUseUserFromHeader() { + HttpHeaders headers = RestHelper.generateHeadersForUser("user-2-1"); + headers.add("userid", "user-1-1"); + ResponseEntity response = + TEMPLATE.exchange( + restHelper.toUrl("/taskana" + RestEndpoints.URL_CURRENT_USER), + HttpMethod.GET, + new HttpEntity<>(headers), + ParameterizedTypeReference.forType(TaskanaUserInfoRepresentationModel.class)); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + TaskanaUserInfoRepresentationModel currentUser = response.getBody(); + assertThat(currentUser).isNotNull(); + assertThat(currentUser.getUserId()).isEqualTo("user-2-1"); + assertThat(currentUser.getGroupIds()).hasSize(2); + assertThat(currentUser.getRoles()).hasSize(1); + } + + @Test + @RunAsClient + public void should_ReturnTask_When_OnlyTheUserInTheUseridHeaderIsAuthorized() { + HttpHeaders headers = RestHelper.generateHeadersForUser("user-1-1"); + headers.add("userid", "teamlead-1"); + ResponseEntity response = + TEMPLATE.exchange( + restHelper.toUrl( + "/taskana" + RestEndpoints.URL_TASKS_ID, + "TKI:000000000000000000000000000000000005"), + HttpMethod.GET, + new HttpEntity<>(headers), + ParameterizedTypeReference.forType(TaskRepresentationModel.class)); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + } + + @Test + @RunAsClient + public void should_ThrowException_When_RequestingTaskFromUnauthorizedUserInTheAdditionalHeader() { + HttpHeaders headers = RestHelper.generateHeadersForUser("teamlead-1"); + headers.add("userid", "user-1-1"); + ThrowingCallable call = + () -> + TEMPLATE.exchange( + restHelper.toUrl( + "/taskana" + RestEndpoints.URL_TASKS_ID, + "TKI:000000000000000000000000000000000005"), + HttpMethod.GET, + new HttpEntity<>(headers), + ParameterizedTypeReference.forType(TaskRepresentationModel.class)); + assertThatThrownBy(call) + .isInstanceOf(HttpStatusCodeException.class) + .extracting(HttpStatusCodeException.class::cast) + .extracting(HttpStatusCodeException::getStatusCode) + .isEqualTo(HttpStatus.FORBIDDEN); + } + + @Test + @RunAsClient + public void should_ThrowException_When_NotUsingAdditionalHeaderBecauseCurrentUserNotAuthorized() { + HttpHeaders headers = RestHelper.generateHeadersForUser("user-2-1"); + headers.add("userid", "teamlead-1"); + ThrowingCallable call = + () -> + TEMPLATE.exchange( + restHelper.toUrl( + "/taskana" + RestEndpoints.URL_TASKS_ID, + "TKI:000000000000000000000000000000000005"), + HttpMethod.GET, + new HttpEntity<>(headers), + ParameterizedTypeReference.forType(TaskRepresentationModel.class)); + assertThatThrownBy(call) + .isInstanceOf(HttpStatusCodeException.class) + .extracting(HttpStatusCodeException.class::cast) + .extracting(HttpStatusCodeException::getStatusCode) + .isEqualTo(HttpStatus.FORBIDDEN); + } +} diff --git a/rest/taskana-rest-spring-example-wildfly/src/test/resources/application-with-additional-user-config.properties b/rest/taskana-rest-spring-example-wildfly/src/test/resources/application-with-additional-user-config.properties new file mode 100644 index 000000000..62be0b224 --- /dev/null +++ b/rest/taskana-rest-spring-example-wildfly/src/test/resources/application-with-additional-user-config.properties @@ -0,0 +1,48 @@ +enableUserIdHeader=true +authorizedUsers=user-1-1|teamlead-1 +######## Taskana DB ####### +datasource.jndi=java:jboss/datasources/TaskanaDS +taskana.schemaName=taskana +####### properties to connect to LDAP +taskana.ldap.serverUrl=ldap://localhost:10389 +taskana.ldap.bindDn=uid=admin +taskana.ldap.bindPassword=secret +taskana.ldap.baseDn=ou=Test,O=TASKANA +taskana.ldap.userSearchBase=cn=users +taskana.ldap.userSearchFilterName=objectclass +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= +taskana.ldap.groupSearchFilterName=objectclass +taskana.ldap.groupSearchFilterValue=groupOfUniqueNames +taskana.ldap.groupNameAttribute=cn +taskana.ldap.minSearchForLength=3 +taskana.ldap.maxNumberOfReturnedAccessIds=50 +taskana.ldap.groupsOfUser=uniquemember +####### JobScheduler cron expression that specifies when the JobSchedler runs +taskana.jobscheduler.async.cron=0 * * * * * +####### cache static resources propertiesgit add -- +spring.resources.cache.cachecontrol.cache-private=true +spring.main.allow-bean-definition-overriding=true +####### tomcat is not detecting the x-forward headers from bluemix as a trustworthy proxy +server.tomcat.remoteip.internal-proxies=.* +server.forward-headers-strategy=native +# Embedded Spring LDAP server +spring.ldap.embedded.base-dn=OU=Test,O=TASKANA +spring.ldap.embedded.credential.username=uid=admin +spring.ldap.embedded.credential.password=secret +spring.ldap.embedded.ldif=classpath:taskana-test.ldif +spring.ldap.embedded.port=10389 +spring.ldap.embedded.validation.enabled=false +