From 4aa8b85bb6ba52b3adb2e7eb230aed918ef7eb5d Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sun, 28 Aug 2022 17:30:27 +0200 Subject: [PATCH] use context.define(...) instead of setCurrent... --- build.gradle | 5 +- .../hsadminng/context/Context.java | 161 +++++++++++------ .../context/HttpServletRequestBodyCache.java | 45 +++++ .../HttpServletRequestBodyCachingFilter.java | 23 +++ .../HttpServletRequestWithCachedBody.java | 33 ++++ .../hs/hscustomer/CustomerController.java | 5 +- .../hs/hspackage/PackageController.java | 5 +- .../rbac/rbacgrant/RbacGrantController.java | 9 +- .../rbac/rbacrole/RbacRoleController.java | 2 +- .../rbac/rbacuser/RbacUserController.java | 17 +- .../resources/db/changelog/010-context.sql | 32 +++- .../hsadminng/context/ContextBasedTest.java | 15 +- .../context/ContextIntegrationTests.java | 7 +- .../hsadminng/context/ContextUnitTest.java | 168 +++++++++++++++++- .../HttpServletRequestBodyCacheUnitTest.java | 111 ++++++++++++ .../CustomerControllerAcceptanceTest.java | 30 ++-- .../PackageControllerAcceptanceTest.java | 13 +- .../PackageRepositoryIntegrationTest.java | 31 +--- .../RbacRoleRepositoryIntegrationTest.java | 97 +++++----- .../RbacUserControllerAcceptanceTest.java | 4 +- 20 files changed, 601 insertions(+), 212 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/context/HttpServletRequestBodyCache.java create mode 100644 src/main/java/net/hostsharing/hsadminng/context/HttpServletRequestBodyCachingFilter.java create mode 100644 src/main/java/net/hostsharing/hsadminng/context/HttpServletRequestWithCachedBody.java create mode 100644 src/test/java/net/hostsharing/hsadminng/context/HttpServletRequestBodyCacheUnitTest.java diff --git a/build.gradle b/build.gradle index 85d22c6b..52519dfb 100644 --- a/build.gradle +++ b/build.gradle @@ -191,7 +191,7 @@ jacocoTestCoverageVerification { 'net.hostsharing.hsadminng.generated.**', 'net.hostsharing.hsadminng.HsadminNgApplication', 'net.hostsharing.hsadminng.TestController', - 'net.hostsharing.hsadminng.Mapper', + 'net.hostsharing.hsadminng.Mapper' ] limit { @@ -205,7 +205,8 @@ jacocoTestCoverageVerification { excludes = [ 'net.hostsharing.hsadminng.generated.**', 'net.hostsharing.hsadminng.HsadminNgApplication.main', - 'net.hostsharing.hsadminng.TestController.*'] + 'net.hostsharing.hsadminng.TestController.*' + ] limit { counter = 'BRANCH' diff --git a/src/main/java/net/hostsharing/hsadminng/context/Context.java b/src/main/java/net/hostsharing/hsadminng/context/Context.java index a0fdf70a..0e461604 100644 --- a/src/main/java/net/hostsharing/hsadminng/context/Context.java +++ b/src/main/java/net/hostsharing/hsadminng/context/Context.java @@ -1,20 +1,33 @@ package net.hostsharing.hsadminng.context; +import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.request.RequestContextHolder; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Collections; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import static java.util.function.Predicate.not; import static org.springframework.transaction.annotation.Propagation.MANDATORY; @Service public class Context { + private static final Set HEADERS_TO_IGNORE = Set.of( + "accept-encoding", + "connection", + "content-length", + "host", + "user-agent"); @PersistenceContext private EntityManager em; @@ -22,81 +35,111 @@ public class Context { private HttpServletRequest request; @Transactional(propagation = MANDATORY) - public void register(final String currentUser, final String assumedRoles) { - if (request != null) { - setCurrentTask(request.getMethod() + " " + request.getRequestURI()); - } else { - - final Optional caller = - StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) - .walk(frames -> - frames.skip(1) - .filter(c -> c.getDeclaringClass() - .getPackageName() - .startsWith("net.hostsharing.hsadminng")) - .filter(c -> !c.getDeclaringClass().getName().contains("BySpringCGLIB$$")) - .findFirst()); - final var callerName = caller.map( - c -> c.getDeclaringClass().getSimpleName() + "." + c.getMethodName()) - .orElse("unknown"); - setCurrentTask(callerName); - } - setCurrentUser(currentUser); - if (!StringUtils.isBlank(assumedRoles)) { - assumeRoles(assumedRoles); - } + public void define(final String currentUser) { + define(currentUser, null); } @Transactional(propagation = MANDATORY) - public void setCurrentTask(final String task) { - final var sql = String.format( - "set local hsadminng.currentTask = '%s';", - shortenToMaxLength(task, 95) - ); - em.createNativeQuery(sql).executeUpdate(); + public void define(final String currentUser, final String assumedRoles) { + define(toTask(request), toCurl(request), currentUser, assumedRoles); + } + + @Transactional(propagation = MANDATORY) + public void define( + final String currentTask, + final String currentRequest, + final String currentUser, + final String assumedRoles) { + final var query = em.createNativeQuery( + "call defineContext(:currentTask, :currentRequest, :currentUser, :assumedRoles);"); + query.setParameter("currentTask", shortenToMaxLength(currentTask, 96)); + query.setParameter("currentRequest", shortenToMaxLength(currentRequest, 512)); // TODO.SPEC: length? + query.setParameter("currentUser", currentUser); + query.setParameter("assumedRoles", assumedRoles != null ? assumedRoles : ""); + query.executeUpdate(); } public String getCurrentTask() { return (String) em.createNativeQuery("select current_setting('hsadminng.currentTask');").getSingleResult(); } - @Transactional(propagation = MANDATORY) - public void setCurrentUser(final String userName) { - em.createNativeQuery( - String.format( - "set local hsadminng.currentUser = '%s';", - userName - ) - ).executeUpdate(); - assumeNoSpecialRole(); - } - public String getCurrentUser() { return String.valueOf(em.createNativeQuery("select currentUser()").getSingleResult()); } - @Transactional(propagation = MANDATORY) - public void assumeRoles(final String roles) { - em.createNativeQuery( - String.format( - "set local hsadminng.assumedRoles = '%s';", - roles - ) - ).executeUpdate(); - } - - @Transactional(propagation = MANDATORY) - public void assumeNoSpecialRole() { - em.createNativeQuery( - "set local hsadminng.assumedRoles = '';" - ).executeUpdate(); - } - public String[] getAssumedRoles() { return (String[]) em.createNativeQuery("select assumedRoles()").getSingleResult(); } - private static String shortenToMaxLength(final String task, final int maxLength) { - return task.substring(0, Math.min(task.length(), maxLength)); + private static String getCallerMethodNameFromStack() { + final Optional caller = + StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) + .walk(frames -> frames + .skip(2) + .filter(c -> c.getDeclaringClass() != Context.class) + .filter(c -> c.getDeclaringClass() + .getPackageName() + .startsWith("net.hostsharing.hsadminng")) + .filter(c -> !c.getDeclaringClass().getName().contains("BySpringCGLIB$$")) + .findFirst()); + return caller.map( + c -> c.getDeclaringClass().getSimpleName() + "." + c.getMethodName()) + .orElse("unknown"); + } + + private String toTask(final HttpServletRequest request) { + if (isRequestScopeAvailable()) { + return request.getMethod() + " " + request.getRequestURI(); + } else { + return getCallerMethodNameFromStack(); + } + } + + @SneakyThrows + private String toCurl(final HttpServletRequest request) { + if (!isRequestScopeAvailable()) { + return null; + } + + var curlCommand = "curl -0 -v"; + + // append method + curlCommand += " -X " + request.getMethod(); + + // append request url + curlCommand += " " + request.getRequestURI(); + + // append headers + final var headers = Collections.list(request.getHeaderNames()).stream() + .filter(not(HEADERS_TO_IGNORE::contains)) + .collect(Collectors.toSet()); + for (String headerName : headers) { + final var headerValue = request.getHeader(headerName); + curlCommand += " \\" + System.lineSeparator() + String.format("-H '%s:%s'", headerName, headerValue); + } + + // body + final String body = request.getReader().lines().collect(Collectors.joining(System.lineSeparator())); + if (!StringUtils.isEmpty(body)) { + curlCommand += " \\" + System.lineSeparator() + "--data-binary @- "; + curlCommand += + "<< EOF" + System.lineSeparator() + System.lineSeparator() + body + System.lineSeparator() + "EOF"; + } + + return curlCommand; + } + + private boolean isRequestScopeAvailable() { + return RequestContextHolder.getRequestAttributes() != null; + } + + private static String shortenToMaxLength(final String raw, final int maxLength) { + if (raw == null) { + return ""; + } + if (raw.length() <= maxLength) { + return raw; + } + return raw.substring(0, maxLength - 3) + "..."; } } diff --git a/src/main/java/net/hostsharing/hsadminng/context/HttpServletRequestBodyCache.java b/src/main/java/net/hostsharing/hsadminng/context/HttpServletRequestBodyCache.java new file mode 100644 index 00000000..25605b62 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/context/HttpServletRequestBodyCache.java @@ -0,0 +1,45 @@ +package net.hostsharing.hsadminng.context; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class HttpServletRequestBodyCache extends ServletInputStream { + + private InputStream inputStream; + + public HttpServletRequestBodyCache(byte[] body) { + this.inputStream = new ByteArrayInputStream(body); + } + + @Override + public int read() throws IOException { + return inputStream.read(); + } + + @Override + public int available() throws IOException { + return inputStream.available(); + } + + @Override + public boolean isFinished() { + try { + return available() == 0; + } catch (IOException e) { + return true; + } + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(final ReadListener listener) { + throw new RuntimeException("Not implemented"); + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/context/HttpServletRequestBodyCachingFilter.java b/src/main/java/net/hostsharing/hsadminng/context/HttpServletRequestBodyCachingFilter.java new file mode 100644 index 00000000..62979d76 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/context/HttpServletRequestBodyCachingFilter.java @@ -0,0 +1,23 @@ +package net.hostsharing.hsadminng.context; + +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class HttpServletRequestBodyCachingFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal( + final HttpServletRequest request, + final HttpServletResponse response, + final FilterChain filterChain) + throws ServletException, IOException { + filterChain.doFilter(new HttpServletRequestWithCachedBody(request), response); + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/context/HttpServletRequestWithCachedBody.java b/src/main/java/net/hostsharing/hsadminng/context/HttpServletRequestWithCachedBody.java new file mode 100644 index 00000000..a3cd22ea --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/context/HttpServletRequestWithCachedBody.java @@ -0,0 +1,33 @@ +package net.hostsharing.hsadminng.context; + +import org.springframework.util.StreamUtils; + +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +public class HttpServletRequestWithCachedBody extends HttpServletRequestWrapper { + + private byte[] cachedBody; + + public HttpServletRequestWithCachedBody(HttpServletRequest request) throws IOException { + super(request); + final var requestInputStream = request.getInputStream(); + this.cachedBody = StreamUtils.copyToByteArray(requestInputStream); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return new HttpServletRequestBodyCache(this.cachedBody); + } + + @Override + public BufferedReader getReader() throws IOException { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody); + return new BufferedReader(new InputStreamReader(byteArrayInputStream)); + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerController.java b/src/main/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerController.java index 2ee61003..47205dec 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerController.java @@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.hscustomer; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.generated.api.v1.api.CustomersApi; import net.hostsharing.hsadminng.generated.api.v1.model.CustomerResource; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; @@ -33,7 +32,7 @@ public class CustomerController implements CustomersApi { String assumedRoles, String prefix ) { - context.register(currentUser, assumedRoles); + context.define(currentUser, assumedRoles); final var result = customerRepository.findCustomerByOptionalPrefixLike(prefix); @@ -47,7 +46,7 @@ public class CustomerController implements CustomersApi { final String assumedRoles, final CustomerResource customer) { - context.register(currentUser, assumedRoles); + context.define(currentUser, assumedRoles); if (customer.getUuid() == null) { customer.setUuid(UUID.randomUUID()); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageController.java b/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageController.java index 148de774..fccc5476 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageController.java @@ -5,7 +5,6 @@ import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.generated.api.v1.api.PackagesApi; import net.hostsharing.hsadminng.generated.api.v1.model.PackageResource; import net.hostsharing.hsadminng.generated.api.v1.model.PackageUpdateResource; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; @@ -33,7 +32,7 @@ public class PackageController implements PackagesApi { String assumedRoles, String name ) { - context.register(currentUser, assumedRoles); + context.define(currentUser, assumedRoles); final var result = packageRepository.findAllByOptionalNameLike(name); return ResponseEntity.ok(mapList(result, PackageResource.class)); @@ -47,7 +46,7 @@ public class PackageController implements PackagesApi { final UUID packageUuid, final PackageUpdateResource body) { - context.register(currentUser, assumedRoles); + context.define(currentUser, assumedRoles); final var current = packageRepository.findByUuid(packageUuid); OptionalFromJson.of(body.getDescription()).ifPresent(current::setDescription); diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantController.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantController.java index bd0e30de..d1eff5a4 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantController.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantController.java @@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.rbac.rbacgrant; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.generated.api.v1.api.RbacgrantsApi; import net.hostsharing.hsadminng.generated.api.v1.model.RbacGrantResource; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; @@ -38,7 +37,7 @@ public class RbacGrantController implements RbacgrantsApi { final UUID grantedRoleUuid, final UUID granteeUserUuid) { - context.register(currentUser, assumedRoles); + context.define(currentUser, assumedRoles); final var id = new RbacGrantId(granteeUserUuid, grantedRoleUuid); final var result = rbacGrantRepository.findById(id); @@ -54,7 +53,7 @@ public class RbacGrantController implements RbacgrantsApi { final String currentUser, final String assumedRoles) { - context.register(currentUser, assumedRoles); + context.define(currentUser, assumedRoles); return ResponseEntity.ok(mapList(rbacGrantRepository.findAll(), RbacGrantResource.class)); } @@ -66,7 +65,7 @@ public class RbacGrantController implements RbacgrantsApi { final String assumedRoles, final RbacGrantResource body) { - context.register(currentUser, assumedRoles); + context.define(currentUser, assumedRoles); final var granted = rbacGrantRepository.save(map(body, RbacGrantEntity.class)); em.flush(); @@ -88,7 +87,7 @@ public class RbacGrantController implements RbacgrantsApi { final UUID grantedRoleUuid, final UUID granteeUserUuid) { - context.register(currentUser, assumedRoles); + context.define(currentUser, assumedRoles); rbacGrantRepository.deleteByRbacGrantId(new RbacGrantId(granteeUserUuid, grantedRoleUuid)); diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java index dd86fde0..1f688591 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java @@ -28,7 +28,7 @@ public class RbacRoleController implements RbacrolesApi { final String currentUser, final String assumedRoles) { - context.register(currentUser, assumedRoles); + context.define(currentUser, assumedRoles); return ResponseEntity.ok(mapList(rbacRoleRepository.findAll(), RbacRoleResource.class)); } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserController.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserController.java index 9a6003f2..90272a78 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserController.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserController.java @@ -2,15 +2,12 @@ package net.hostsharing.hsadminng.rbac.rbacuser; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.generated.api.v1.api.RbacusersApi; -import net.hostsharing.hsadminng.generated.api.v1.model.RbacGrantResource; import net.hostsharing.hsadminng.generated.api.v1.model.RbacUserPermissionResource; import net.hostsharing.hsadminng.generated.api.v1.model.RbacUserResource; -import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantId; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import java.util.List; @@ -33,7 +30,7 @@ public class RbacUserController implements RbacusersApi { public ResponseEntity createUser( final RbacUserResource body ) { - context.register(body.getName(), null); + context.define(body.getName()); if (body.getUuid() == null) { body.setUuid(UUID.randomUUID()); @@ -55,7 +52,7 @@ public class RbacUserController implements RbacusersApi { final String assumedRoles, final UUID userUuid) { - context.register(currentUser, assumedRoles); + context.define(currentUser, assumedRoles); final var result = rbacUserRepository.findByUuid(userUuid); if (result == null) { @@ -71,7 +68,7 @@ public class RbacUserController implements RbacusersApi { final String assumedRoles, final String userName ) { - context.register(currentUser, assumedRoles); + context.define(currentUser, assumedRoles); return ResponseEntity.ok(mapList(rbacUserRepository.findByOptionalNameLike(userName), RbacUserResource.class)); } @@ -83,8 +80,10 @@ public class RbacUserController implements RbacusersApi { final String assumedRoles, final UUID userUuid ) { - context.register(currentUser, assumedRoles); + context.define(currentUser, assumedRoles); - return ResponseEntity.ok(mapList(rbacUserRepository.findPermissionsOfUserByUuid(userUuid), RbacUserPermissionResource.class)); + return ResponseEntity.ok(mapList( + rbacUserRepository.findPermissionsOfUserByUuid(userUuid), + RbacUserPermissionResource.class)); } } diff --git a/src/main/resources/db/changelog/010-context.sql b/src/main/resources/db/changelog/010-context.sql index 95bc003d..f42efd99 100644 --- a/src/main/resources/db/changelog/010-context.sql +++ b/src/main/resources/db/changelog/010-context.sql @@ -1,10 +1,38 @@ --liquibase formatted sql + +-- ============================================================================ +--changeset context-DEFINE:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +/* + Defines the transaction context. + */ + +create or replace procedure defineContext( + currentTask varchar, + currentRequest varchar, + currentUser varchar, + assumedRoles varchar +) + language plpgsql as $$ +begin + raise notice 'currentRequest: %', defineContext.currentRequest; + execute format('set local hsadminng.currentTask to %L', currentTask); + execute format('set local hsadminng.currentUser to %L', currentUser); + if length(assumedRoles) > 0 then + execute format('set local hsadminng.assumedRoles to %L', assumedRoles); + else + execute format('set local hsadminng.assumedRoles to %L', ''); + end if; +end; $$; +--// + + -- ============================================================================ --changeset context-CURRENT-TASK:1 endDelimiter:--// -- ---------------------------------------------------------------------------- /* - Returns the current tas as set by `hsadminng.currentTask`. + Returns the current task as set by `hsadminng.currentTask`. Raises exception if not set. */ create or replace function currentTask() @@ -117,7 +145,7 @@ create or replace function findIdNameByObjectUuid(objectTable varchar, objectUui returns null on null input language plpgsql as $$ declare - sql varchar; + sql varchar; idName varchar; begin objectTable := pureIdentifier(objectTable); diff --git a/src/test/java/net/hostsharing/hsadminng/context/ContextBasedTest.java b/src/test/java/net/hostsharing/hsadminng/context/ContextBasedTest.java index 67340a18..87946ce4 100644 --- a/src/test/java/net/hostsharing/hsadminng/context/ContextBasedTest.java +++ b/src/test/java/net/hostsharing/hsadminng/context/ContextBasedTest.java @@ -4,8 +4,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInfo; import org.springframework.beans.factory.annotation.Autowired; -import static org.assertj.core.api.Assertions.assertThat; - public class ContextBasedTest { @Autowired @@ -18,18 +16,9 @@ public class ContextBasedTest { this.test = testInfo; } + // TODO: remove the class and check which task is recorded protected void context(final String currentUser, final String assumedRoles) { - context.setCurrentTask(test.getDisplayName()); - - context.setCurrentUser(currentUser); - assertThat(context.getCurrentUser()).as("precondition").isEqualTo(currentUser); - - if (assumedRoles != null) { - context.assumeRoles(assumedRoles); - assertThat(context.getAssumedRoles()).as("precondition").containsExactly(assumedRoles.split(";")); -// } else { -// context.assumeNoSpecialRole(); - } + context.define(test.getDisplayName(), null, currentUser, assumedRoles); } protected void context(final String currentUser) { diff --git a/src/test/java/net/hostsharing/hsadminng/context/ContextIntegrationTests.java b/src/test/java/net/hostsharing/hsadminng/context/ContextIntegrationTests.java index 98324ff9..1214c5b9 100644 --- a/src/test/java/net/hostsharing/hsadminng/context/ContextIntegrationTests.java +++ b/src/test/java/net/hostsharing/hsadminng/context/ContextIntegrationTests.java @@ -20,7 +20,7 @@ class ContextIntegrationTests { @Test void registerWithoutHttpServletRequestUsesCallStack() { - context.register("current-user", null); + context.define("current-user", null); final var currentTask = context.getCurrentTask(); assertThat(currentTask).isEqualTo("ContextIntegrationTests.registerWithoutHttpServletRequestUsesCallStack"); @@ -29,7 +29,7 @@ class ContextIntegrationTests { @Test @Transactional void setCurrentUser() { - context.setCurrentUser("mike@hostsharing.net"); + context.define("mike@hostsharing.net"); final var currentUser = context.getCurrentUser(); assertThat(currentUser).isEqualTo("mike@hostsharing.net"); @@ -41,8 +41,7 @@ class ContextIntegrationTests { @Test @Transactional void assumeRoles() { - context.setCurrentUser("mike@hostsharing.net"); - context.assumeRoles("customer#xxx.owner;customer#yyy.owner"); + context.define("mike@hostsharing.net", "customer#xxx.owner;customer#yyy.owner"); final var currentUser = context.getCurrentUser(); assertThat(currentUser).isEqualTo("mike@hostsharing.net"); diff --git a/src/test/java/net/hostsharing/hsadminng/context/ContextUnitTest.java b/src/test/java/net/hostsharing/hsadminng/context/ContextUnitTest.java index e12b79f3..fec12cd9 100644 --- a/src/test/java/net/hostsharing/hsadminng/context/ContextUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/context/ContextUnitTest.java @@ -1,15 +1,25 @@ package net.hostsharing.hsadminng.context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; import javax.persistence.EntityManager; import javax.persistence.Query; +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; -import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; @@ -22,16 +32,156 @@ class ContextUnitTest { @Mock Query nativeQuery; - @InjectMocks - Context context; + @Nested + class WithoutHttpRequest { - @Test - void registerWithoutHttpServletRequestUsesCallStack() { - given(em.createNativeQuery(any())).willReturn(nativeQuery); + @Mock + EntityManager em; - context.register("current-user", null); + @Mock + Query nativeQuery; - verify(em).createNativeQuery( - "set local hsadminng.currentTask = 'ContextUnitTest.registerWithoutHttpServletRequestUsesCallStack';"); + @InjectMocks + Context context; + + @BeforeEach + void setup() { + RequestContextHolder.setRequestAttributes(null); + given(em.createNativeQuery(any())).willReturn(nativeQuery); + } + + @Test + void registerWithoutHttpServletRequestUsesCallStackForTask() { + given(em.createNativeQuery(any())).willReturn(nativeQuery); + + context.define("current-user"); + + verify(em).createNativeQuery("call defineContext(:currentTask, :currentRequest, :currentUser, :assumedRoles);"); + verify(nativeQuery).setParameter( + "currentTask", + "WithoutHttpRequest.registerWithoutHttpServletRequestUsesCallStackForTask"); + } + + @Test + void registerWithoutHttpServletRequestUsesEmptyStringForRequest() { + given(em.createNativeQuery(any())).willReturn(nativeQuery); + + context.define("current-user"); + + verify(em).createNativeQuery("call defineContext(:currentTask, :currentRequest, :currentUser, :assumedRoles);"); + verify(nativeQuery).setParameter("currentRequest", ""); + } + } + + @Nested + class WithHttpRequest { + + @Mock + EntityManager em; + + @Mock + Query nativeQuery; + + @Mock + HttpServletRequest request; + + @Mock + RequestAttributes requestAttributes; + + @Mock + BufferedReader requestBodyReader; + + @Mock + Stream requestBodyLines; + + @InjectMocks + Context context; + + @BeforeEach + void setup() { + RequestContextHolder.setRequestAttributes(requestAttributes); + given(em.createNativeQuery(any())).willReturn(nativeQuery); + } + + @Test + void registerWithHttpServletRequestUsesRequest() throws IOException { + givenRequest("POST", "http://localhost:9999/api/endpoint", Map.ofEntries( + Map.entry("current-user", "given-user"), + Map.entry("content-type", "application/json"), + Map.entry("user-agent", "given-user-agent")), + "{}"); + + context.define("current-user"); + + verify(em).createNativeQuery("call defineContext(:currentTask, :currentRequest, :currentUser, :assumedRoles);"); + verify(nativeQuery).setParameter("currentTask", "POST http://localhost:9999/api/endpoint"); + } + + @Test + void registerWithHttpServletRequestForwardsRequestAsCurl() throws IOException { + givenRequest("POST", "http://localhost:9999/api/endpoint", Map.ofEntries( + Map.entry("current-user", "given-user"), + Map.entry("content-type", "application/json"), + Map.entry("user-agent", "given-user-agent")), + "{}"); + + context.define("current-user"); + + verify(em).createNativeQuery("call defineContext(:currentTask, :currentRequest, :currentUser, :assumedRoles);"); + verify(nativeQuery).setParameter("currentRequest", """ + curl -0 -v -X POST http://localhost:9999/api/endpoint \\ + -H 'current-user:given-user' \\ + -H 'content-type:application/json' \\ + --data-binary @- << EOF + + {} + EOF + """.trim()); + } + + @Test + void shortensCurrentTaskTo96Chars() throws IOException { + givenRequest("GET", "http://localhost:9999/api/endpoint/" + "0123456789".repeat(10), + Map.ofEntries( + Map.entry("current-user", "given-user"), + Map.entry("content-type", "application/json"), + Map.entry("user-agent", "given-user-agent")), + "{}"); + + context.define("current-user"); + + verify(em).createNativeQuery("call defineContext(:currentTask, :currentRequest, :currentUser, :assumedRoles);"); + verify(nativeQuery).setParameter(eq("currentTask"), argThat((String t) -> t.length() == 96)); + } + + @Test + void shortensCurrentRequestTo512Chars() throws IOException { + givenRequest("GET", "http://localhost:9999/api/endpoint", + Map.ofEntries( + Map.entry("current-user", "given-user"), + Map.entry("content-type", "application/json"), + Map.entry("user-agent", "given-user-agent")), + """ + { + "dummy": "%s" + } + """.formatted("0123456789".repeat(60))); + + context.define("current-user"); + + verify(em).createNativeQuery("call defineContext(:currentTask, :currentRequest, :currentUser, :assumedRoles);"); + verify(nativeQuery).setParameter(eq("currentRequest"), argThat((String t) -> t.length() == 512)); + } + + private void givenRequest(final String method, final String url, final Map headers, final String body) + throws IOException { + given(request.getMethod()).willReturn(method); + given(request.getRequestURI()).willReturn(url); + given(request.getHeaderNames()).willReturn(Collections.enumeration(headers.keySet())); + given(request.getHeader(anyString())).willAnswer(invocation -> headers.get(invocation.getArgument(0).toString())); + given(request.getReader()).willReturn(requestBodyReader); + given(requestBodyReader.lines()).willReturn(requestBodyLines); + given(requestBodyLines.collect(any())).willReturn(body); + } } } diff --git a/src/test/java/net/hostsharing/hsadminng/context/HttpServletRequestBodyCacheUnitTest.java b/src/test/java/net/hostsharing/hsadminng/context/HttpServletRequestBodyCacheUnitTest.java new file mode 100644 index 00000000..4903b594 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/context/HttpServletRequestBodyCacheUnitTest.java @@ -0,0 +1,111 @@ +package net.hostsharing.hsadminng.context; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.spy; + +class HttpServletRequestBodyCacheUnitTest { + + @Test + void readsTheStream() { + // given + try (final var givenBodyCache = new HttpServletRequestBodyCache("Hallo".getBytes())) { + + // when + final var actual = new String(givenBodyCache.readAllBytes()); + + // then + assertThat(actual).isEqualTo("Hallo"); + + } catch (final IOException exc) { + throw new AssertionError("unexpected IO exception", exc); + } + } + + @Test + void isReadyReturnsTrue() { + // given + try (final var givenBodyCache = new HttpServletRequestBodyCache("Hallo".getBytes())) { + + // when + final var actual = givenBodyCache.isReady(); + + // then + assertThat(actual).isTrue(); + + } catch (final IOException exc) { + throw new AssertionError("unexpected IO exception", exc); + } + } + + @Test + void isFinishedReturnsTrueWhenNotRead() { + // given + try (final var givenBodyCache = new HttpServletRequestBodyCache("Hallo".getBytes())) { + + // when + final var actual = givenBodyCache.isFinished(); + + // then + assertThat(actual).isFalse(); + + } catch (final IOException exc) { + throw new AssertionError("unexpected IO exception", exc); + } + } + + @Test + void isFinishedReturnsTrueWhenRead() { + // given + try (final var givenBodyCache = new HttpServletRequestBodyCache("Hallo".getBytes())) { + givenBodyCache.readAllBytes(); + + // when + final var actual = givenBodyCache.isFinished(); + + // then + assertThat(actual).isTrue(); + + } catch (final IOException exc) { + throw new AssertionError("unexpected IO exception", exc); + } + } + + @Test + void isFinishedReturnsTrueOnException() { + // given + try (final var givenBodyCache = spy(new HttpServletRequestBodyCache("".getBytes()))) { + given(givenBodyCache.available()).willThrow(new IOException("fake exception")); + + // when + final var actual = givenBodyCache.isFinished(); + + // then + assertThat(actual).isTrue(); + + } catch (final IOException exc) { + throw new AssertionError("unexpected IO exception", exc); + } + } + + @Test + void setReadListenerThrowsNotImplementedException() { + // given + try (final var givenBodyCache = new HttpServletRequestBodyCache("Hallo".getBytes())) { + + // when + final var exception = assertThrows(RuntimeException.class, () -> givenBodyCache.setReadListener(null)); + + // then + assertThat(exception.getMessage()).isEqualTo("Not implemented"); + + } catch (final IOException exc) { + throw new AssertionError("unexpected IO exception", exc); + } + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerControllerAcceptanceTest.java index 0a9f0955..2c8212f0 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerControllerAcceptanceTest.java @@ -57,7 +57,7 @@ class CustomerControllerAcceptanceTest { } @Test - void hostsharingAdmin_withoutAssumedRoles_canViewMatchingCustomers_ifCriteriaGiven() throws Exception { + void hostsharingAdmin_withoutAssumedRoles_canViewMatchingCustomers_ifCriteriaGiven() { RestAssured // @formatter:off .given() .header("current-user", "mike@hostsharing.net") @@ -73,7 +73,7 @@ class CustomerControllerAcceptanceTest { } @Test - void hostsharingAdmin_withoutAssumedCustomerAdminRole_canOnlyViewOwnCustomer() throws Exception { + void hostsharingAdmin_withoutAssumedCustomerAdminRole_canOnlyViewOwnCustomer() { RestAssured // @formatter:off .given() .header("current-user", "mike@hostsharing.net") @@ -90,14 +90,14 @@ class CustomerControllerAcceptanceTest { } @Test - void customerAdmin_withoutAssumedRole_canOnlyViewOwnCustomer() throws Exception { + void customerAdmin_withoutAssumedRole_canOnlyViewOwnCustomer() { RestAssured // @formatter:off - .given() + .given() .header("current-user", "customer-admin@yyy.example.com") .port(port) - .when() + .when() .get("http://localhost/api/customers") - .then().assertThat() + .then().assertThat() .statusCode(200) .contentType("application/json") .body("[0].prefix", is("yyy")) @@ -107,10 +107,10 @@ class CustomerControllerAcceptanceTest { } @Nested - class CreateCustomer { + class AddCustomer { @Test - void hostsharingAdmin_withoutAssumedRole_canCreateCustomer() throws Exception { + void hostsharingAdmin_withoutAssumedRole_canAddCustomer() { final var location = RestAssured // @formatter:off .given() @@ -136,13 +136,13 @@ class CustomerControllerAcceptanceTest { // finally, the new customer can be viewed by its own admin final var newUserUuid = UUID.fromString( location.substring(location.lastIndexOf('/') + 1)); - context.setCurrentUser("customer-admin@ttt.example.com"); + context.define("customer-admin@ttt.example.com"); assertThat(customerRepository.findByUuid(newUserUuid)) .hasValueSatisfying(c -> assertThat(c.getPrefix()).isEqualTo("ttt")); } @Test - void hostsharingAdmin_withoutAssumedRole_canCreateCustomerWithGivenUuid() { + void hostsharingAdmin_withoutAssumedRole_canAddCustomerWithGivenUuid() { final var givenUuid = UUID.randomUUID(); @@ -171,7 +171,7 @@ class CustomerControllerAcceptanceTest { // finally, the new customer can be viewed by its own admin final var newUserUuid = UUID.fromString( location.substring(location.lastIndexOf('/') + 1)); - context.setCurrentUser("customer-admin@vvv.example.com"); + context.define("customer-admin@vvv.example.com"); assertThat(customerRepository.findByUuid(newUserUuid)) .hasValueSatisfying(c -> { assertThat(c.getPrefix()).isEqualTo("vvv"); @@ -180,7 +180,7 @@ class CustomerControllerAcceptanceTest { } @Test - void hostsharingAdmin_withAssumedCustomerAdminRole_canNotCreateCustomer() throws Exception { + void hostsharingAdmin_withAssumedCustomerAdminRole_canNotAddCustomer() { RestAssured // @formatter:off .given() @@ -205,12 +205,12 @@ class CustomerControllerAcceptanceTest { // @formatter:on // finally, the new customer was not created - context.setCurrentUser("sven@hostsharing.net"); + context.define("sven@hostsharing.net"); assertThat(customerRepository.findCustomerByOptionalPrefixLike("uuu")).hasSize(0); } @Test - void customerAdmin_withoutAssumedRole_canNotCreateCustomer() throws Exception { + void customerAdmin_withoutAssumedRole_canNotAddCustomer() { RestAssured // @formatter:off .given() @@ -234,7 +234,7 @@ class CustomerControllerAcceptanceTest { // @formatter:on // finally, the new customer was not created - context.setCurrentUser("sven@hostsharing.net"); + context.define("sven@hostsharing.net"); assertThat(customerRepository.findCustomerByOptionalPrefixLike("uuu")).hasSize(0); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerAcceptanceTest.java index dab9934d..f7a344bc 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerAcceptanceTest.java @@ -21,8 +21,8 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @SpringBootTest( - webEnvironment = WebEnvironment.RANDOM_PORT, - classes = HsadminNgApplication.class + webEnvironment = WebEnvironment.RANDOM_PORT, + classes = HsadminNgApplication.class ) @Transactional class PackageControllerAcceptanceTest { @@ -86,7 +86,7 @@ class PackageControllerAcceptanceTest { void withDescriptionUpdatesDescription() { assumeThat(getDescriptionOfPackage("xxx00")) - .isEqualTo("Here can add your own description of package xxx00."); + .isEqualTo("Here can add your own description of package xxx00."); final var randomDescription = RandomStringUtils.randomAlphanumeric(80); @@ -118,7 +118,7 @@ class PackageControllerAcceptanceTest { void withNullDescriptionUpdatesDescriptionToNull() { assumeThat(getDescriptionOfPackage("xxx01")) - .isEqualTo("Here can add your own description of package xxx01."); + .isEqualTo("Here can add your own description of package xxx01."); // @formatter:off RestAssured @@ -147,7 +147,7 @@ class PackageControllerAcceptanceTest { void withoutDescriptionDoesNothing() { assumeThat(getDescriptionOfPackage("xxx02")) - .isEqualTo("Here can add your own description of package xxx02."); + .isEqualTo("Here can add your own description of package xxx02."); // @formatter:off RestAssured @@ -185,8 +185,7 @@ class PackageControllerAcceptanceTest { } String getDescriptionOfPackage(final String packageName) { - context.setCurrentUser("mike@hostsharing.net"); - context.assumeRoles("customer#xxx.admin"); + context.define("mike@hostsharing.net","customer#xxx.admin"); return packageRepository.findAllByOptionalNameLike(packageName).get(0).getDescription(); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageRepositoryIntegrationTest.java index 170ac919..fa326ed4 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageRepositoryIntegrationTest.java @@ -42,7 +42,7 @@ class PackageRepositoryIntegrationTest { @Test public void hostsharingAdmin_withoutAssumedRole_canNotViewAnyPackages_becauseThoseGrantsAreNotassumedd() { // given - currentUser("mike@hostsharing.net"); + context.define("mike@hostsharing.net"); // when final var result = packageRepository.findAllByOptionalNameLike(null); @@ -54,8 +54,7 @@ class PackageRepositoryIntegrationTest { @Test public void hostsharingAdmin_withAssumedHostsharingAdminRole__canNotViewAnyPackages_becauseThoseGrantsAreNotassumedd() { given: - currentUser("mike@hostsharing.net"); - assumedRoles("global#hostsharing.admin"); + context.define("mike@hostsharing.net", "global#hostsharing.admin"); // when final var result = packageRepository.findAllByOptionalNameLike(null); @@ -67,7 +66,7 @@ class PackageRepositoryIntegrationTest { @Test public void customerAdmin_withoutAssumedRole_canViewOnlyItsOwnPackages() { // given: - currentUser("customer-admin@xxx.example.com"); + context.define("customer-admin@xxx.example.com"); // when: final var result = packageRepository.findAllByOptionalNameLike(null); @@ -78,8 +77,7 @@ class PackageRepositoryIntegrationTest { @Test public void customerAdmin_withAssumedOwnedPackageAdminRole_canViewOnlyItsOwnPackages() { - currentUser("customer-admin@xxx.example.com"); - assumedRoles("package#xxx00.admin"); + context.define("customer-admin@xxx.example.com", "package#xxx00.admin"); final var result = packageRepository.findAllByOptionalNameLike(null); @@ -89,8 +87,7 @@ class PackageRepositoryIntegrationTest { @Test public void customerAdmin_withAssumedAlienPackageAdminRole_cannotViewAnyPackages() { // given: - currentUser("customer-admin@xxx.example.com"); - assumedRoles("package#yyy00.admin"); + context.define("customer-admin@xxx.example.com", "package#yyy00.admin"); // when final var result = attempt( @@ -105,7 +102,7 @@ class PackageRepositoryIntegrationTest { @Test void unknownUser_withoutAssumedRole_cannotViewAnyPackages() { - currentUser("unknown@example.org"); + context.define("unknown@example.org"); final var result = attempt( em, @@ -119,8 +116,7 @@ class PackageRepositoryIntegrationTest { @Test @Transactional void unknownUser_withAssumedCustomerRole_cannotViewAnyPackages() { - currentUser("unknown@example.org"); - assumedRoles("customer#xxx.admin"); + context.define("unknown@example.org", "customer#xxx.admin"); final var result = attempt( em, @@ -172,18 +168,7 @@ class PackageRepositoryIntegrationTest { } private void hostsharingAdminWithAssumedRole(final String assumedRoles) { - currentUser("mike@hostsharing.net"); - assumedRoles(assumedRoles); - } - - void currentUser(final String currentUser) { - context.setCurrentUser(currentUser); - assertThat(context.getCurrentUser()).as("precondition").isEqualTo(currentUser); - } - - void assumedRoles(final String assumedRoles) { - context.assumeRoles(assumedRoles); - assertThat(context.getAssumedRoles()).as("precondition").containsExactly(assumedRoles.split(";")); + context.define("mike@hostsharing.net", assumedRoles); } void noPackagesAreReturned(final List actualResult) { diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleRepositoryIntegrationTest.java index 92bcd456..7bc85bd6 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleRepositoryIntegrationTest.java @@ -27,13 +27,14 @@ class RbacRoleRepositoryIntegrationTest { @Autowired RbacRoleRepository rbacRoleRepository; - @Autowired EntityManager em; + @Autowired + EntityManager em; @Nested class FindAllRbacRoles { private static final String[] ALL_TEST_DATA_ROLES = Array.of( - // @formatter:off + // @formatter:off "global#hostsharing.admin", "customer#xxx.admin", "customer#xxx.owner", "customer#xxx.tenant", "package#xxx00.admin", "package#xxx00.owner", "package#xxx00.tenant", @@ -53,7 +54,7 @@ class RbacRoleRepositoryIntegrationTest { @Test public void hostsharingAdmin_withoutAssumedRole_canViewAllRbacRoles() { // given - currentUser("mike@hostsharing.net"); + context.define("mike@hostsharing.net"); // when final var result = rbacRoleRepository.findAll(); @@ -65,8 +66,7 @@ class RbacRoleRepositoryIntegrationTest { @Test public void hostsharingAdmin_withAssumedHostsharingAdminRole_canViewAllRbacRoles() { given: - currentUser("mike@hostsharing.net"); - assumedRoles("global#hostsharing.admin"); + context.define("mike@hostsharing.net", "global#hostsharing.admin"); // when final var result = rbacRoleRepository.findAll(); @@ -78,15 +78,15 @@ class RbacRoleRepositoryIntegrationTest { @Test public void customerAdmin_withoutAssumedRole_canViewOnlyItsOwnRbacRole() { // given: - currentUser("customer-admin@xxx.example.com"); + context.define("customer-admin@xxx.example.com"); // when: final var result = rbacRoleRepository.findAll(); // then: allTheseRbacRolesAreReturned( - result, - // @formatter:off + result, + // @formatter:off "customer#xxx.admin", "customer#xxx.tenant", "package#xxx00.admin", @@ -104,8 +104,8 @@ class RbacRoleRepositoryIntegrationTest { // @formatter:on ); noneOfTheseRbacRolesIsReturned( - result, - // @formatter:off + result, + // @formatter:off "global#hostsharing.admin", "customer#xxx.owner", "package#yyy00.admin", @@ -117,64 +117,61 @@ class RbacRoleRepositoryIntegrationTest { @Test public void customerAdmin_withAssumedOwnedPackageAdminRole_canViewOnlyItsOwnRbacRole() { - currentUser("customer-admin@xxx.example.com"); - assumedRoles("package#xxx00.admin"); + context.define("customer-admin@xxx.example.com", "package#xxx00.admin"); final var result = rbacRoleRepository.findAll(); exactlyTheseRbacRolesAreReturned( - result, - "customer#xxx.tenant", - "package#xxx00.admin", - "package#xxx00.tenant", - "unixuser#xxx00-aaaa.admin", - "unixuser#xxx00-aaaa.owner", - "unixuser#xxx00-aaab.admin", - "unixuser#xxx00-aaab.owner"); + result, + "customer#xxx.tenant", + "package#xxx00.admin", + "package#xxx00.tenant", + "unixuser#xxx00-aaaa.admin", + "unixuser#xxx00-aaaa.owner", + "unixuser#xxx00-aaab.admin", + "unixuser#xxx00-aaab.owner"); } @Test public void customerAdmin_withAssumedAlienPackageAdminRole_cannotViewAnyRbacRole() { // given: - currentUser("customer-admin@xxx.example.com"); - assumedRoles("package#yyy00.admin"); + context.define("customer-admin@xxx.example.com", "package#yyy00.admin"); // when final var result = attempt( - em, - () -> rbacRoleRepository.findAll()); + em, + () -> rbacRoleRepository.findAll()); // then result.assertExceptionWithRootCauseMessage( - JpaSystemException.class, - "[403] user customer-admin@xxx.example.com", "has no permission to assume role package#yyy00#admin"); + JpaSystemException.class, + "[403] user customer-admin@xxx.example.com", "has no permission to assume role package#yyy00#admin"); } @Test void unknownUser_withoutAssumedRole_cannotViewAnyRbacRoles() { - currentUser("unknown@example.org"); + context.define("unknown@example.org"); final var result = attempt( - em, - () -> rbacRoleRepository.findAll()); + em, + () -> rbacRoleRepository.findAll()); result.assertExceptionWithRootCauseMessage( - JpaSystemException.class, - "hsadminng.currentUser defined as unknown@example.org, but does not exists"); + JpaSystemException.class, + "hsadminng.currentUser defined as unknown@example.org, but does not exists"); } @Test void unknownUser_withAssumedRbacRoleRole_cannotViewAnyRbacRoles() { - currentUser("unknown@example.org"); - assumedRoles("RbacRole#xxx.admin"); + context.define("unknown@example.org", "RbacRole#xxx.admin"); final var result = attempt( - em, - () -> rbacRoleRepository.findAll()); + em, + () -> rbacRoleRepository.findAll()); result.assertExceptionWithRootCauseMessage( - JpaSystemException.class, - "hsadminng.currentUser defined as unknown@example.org, but does not exists"); + JpaSystemException.class, + "hsadminng.currentUser defined as unknown@example.org, but does not exists"); } } @@ -183,7 +180,7 @@ class RbacRoleRepositoryIntegrationTest { @Test void customerAdmin_withoutAssumedRole_canFindItsOwnRolesByName() { - currentUser("customer-admin@xxx.example.com"); + context.define("customer-admin@xxx.example.com"); final var result = rbacRoleRepository.findByRoleName("customer#xxx.admin"); @@ -195,7 +192,7 @@ class RbacRoleRepositoryIntegrationTest { @Test void customerAdmin_withoutAssumedRole_canNotFindAlienRolesByName() { - currentUser("customer-admin@xxx.example.com"); + context.define("customer-admin@xxx.example.com"); final var result = rbacRoleRepository.findByRoleName("customer#bbb.admin"); @@ -203,32 +200,22 @@ class RbacRoleRepositoryIntegrationTest { } } - void currentUser(final String currentUser) { - context.setCurrentUser(currentUser); - assertThat(context.getCurrentUser()).as("precondition").isEqualTo(currentUser); - } - - void assumedRoles(final String assumedRoles) { - context.assumeRoles(assumedRoles); - assertThat(context.getAssumedRoles()).as("precondition").containsExactly(assumedRoles.split(";")); - } - void exactlyTheseRbacRolesAreReturned(final List actualResult, final String... expectedRoleNames) { assertThat(actualResult) - .extracting(RbacRoleEntity::getRoleName) - .containsExactlyInAnyOrder(expectedRoleNames); + .extracting(RbacRoleEntity::getRoleName) + .containsExactlyInAnyOrder(expectedRoleNames); } void allTheseRbacRolesAreReturned(final List actualResult, final String... expectedRoleNames) { assertThat(actualResult) - .extracting(RbacRoleEntity::getRoleName) - .contains(expectedRoleNames); + .extracting(RbacRoleEntity::getRoleName) + .contains(expectedRoleNames); } void noneOfTheseRbacRolesIsReturned(final List actualResult, final String... unexpectedRoleNames) { assertThat(actualResult) - .extracting(RbacRoleEntity::getRoleName) - .doesNotContain(unexpectedRoleNames); + .extracting(RbacRoleEntity::getRoleName) + .doesNotContain(unexpectedRoleNames); } } diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerAcceptanceTest.java index 7234d8a9..ce1670a9 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerAcceptanceTest.java @@ -71,7 +71,7 @@ class RbacUserControllerAcceptanceTest { // finally, the user can view its own record final var newUserUuid = UUID.fromString( location.substring(location.lastIndexOf('/') + 1)); - context.setCurrentUser("new-user@example.com"); + context.define("new-user@example.com"); assertThat(rbacUserRepository.findByUuid(newUserUuid)) .extracting(RbacUserEntity::getName).isEqualTo("new-user@example.com"); } @@ -399,7 +399,7 @@ class RbacUserControllerAcceptanceTest { RbacUserEntity findRbacUserByName(final String userName) { return jpaAttempt.transacted(() -> { - context.setCurrentUser("mike@hostsharing.net"); + context.define("mike@hostsharing.net"); return rbacUserRepository.findByName(userName); }).returnedValue(); }