From f5ac75233412ab7ae836c909935d232411ea17f2 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 17 Oct 2024 15:45:41 +0200 Subject: [PATCH] add fallback realm role to `@VaultRole` annotation The fallback is (currently) only used in case of `onMissingVault=REQUIRE_REALM_ROLE`, when no vault role exists yet --- .../cryptomator/hub/filters/VaultRole.java | 10 +++++- .../hub/filters/VaultRoleFilter.java | 6 ++++ .../hub/filters/VaultRoleFilterTest.java | 34 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/cryptomator/hub/filters/VaultRole.java b/backend/src/main/java/org/cryptomator/hub/filters/VaultRole.java index 0dad890d..39e4098a 100644 --- a/backend/src/main/java/org/cryptomator/hub/filters/VaultRole.java +++ b/backend/src/main/java/org/cryptomator/hub/filters/VaultRole.java @@ -31,5 +31,13 @@ * @return How to treat the case when a vault does not exist. */ OnMissingVault onMissingVault() default OnMissingVault.FORBIDDEN; - enum OnMissingVault { FORBIDDEN, NOT_FOUND, PASS } + enum OnMissingVault { FORBIDDEN, NOT_FOUND, PASS, REQUIRE_REALM_ROLE } + + /** + * Which additional realm role is required to access the annotated resource. + * + * Only relevant if {@link #onMissingVault()} is set to {@link OnMissingVault#REQUIRE_REALM_ROLE}. + * @return realm role required to access the annotated resource. + */ + String realmRole() default ""; } diff --git a/backend/src/main/java/org/cryptomator/hub/filters/VaultRoleFilter.java b/backend/src/main/java/org/cryptomator/hub/filters/VaultRoleFilter.java index 6cb24d8a..ca94d360 100644 --- a/backend/src/main/java/org/cryptomator/hub/filters/VaultRoleFilter.java +++ b/backend/src/main/java/org/cryptomator/hub/filters/VaultRoleFilter.java @@ -32,6 +32,7 @@ public class VaultRoleFilter implements ContainerRequestFilter { @Inject EffectiveVaultAccess.Repository effectiveVaultAccessRepo; + @Inject Vault.Repository vaultRepo; @@ -67,6 +68,11 @@ public void filter(ContainerRequestContext requestContext) throws NotFoundExcept case FORBIDDEN -> throw new ForbiddenException(forbiddenMsg); case NOT_FOUND -> throw new NotFoundException("Vault not found"); case PASS -> {} + case REQUIRE_REALM_ROLE -> { + if (!requestContext.getSecurityContext().isUserInRole(annotation.realmRole())) { + throw new ForbiddenException("Missing role " + annotation.realmRole()); + } + } } } } diff --git a/backend/src/test/java/org/cryptomator/hub/filters/VaultRoleFilterTest.java b/backend/src/test/java/org/cryptomator/hub/filters/VaultRoleFilterTest.java index b6a2368a..34049a81 100644 --- a/backend/src/test/java/org/cryptomator/hub/filters/VaultRoleFilterTest.java +++ b/backend/src/test/java/org/cryptomator/hub/filters/VaultRoleFilterTest.java @@ -6,6 +6,7 @@ import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ResourceInfo; import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.SecurityContext; import jakarta.ws.rs.core.UriInfo; import org.cryptomator.hub.entities.EffectiveVaultAccess; import org.cryptomator.hub.entities.Vault; @@ -28,6 +29,7 @@ public class VaultRoleFilterTest { private final ResourceInfo resourceInfo = Mockito.mock(ResourceInfo.class); private final UriInfo uriInfo = Mockito.mock(UriInfo.class); private final ContainerRequestContext context = Mockito.mock(ContainerRequestContext.class); + private final SecurityContext securityContext = Mockito.mock(SecurityContext.class); private final JsonWebToken jwt = Mockito.mock(JsonWebToken.class); private final EffectiveVaultAccess.Repository effectiveVaultAccessRepo = Mockito.mock(EffectiveVaultAccess.Repository.class); private final Vault.Repository vaultRepo = Mockito.mock(Vault.Repository.class); @@ -41,6 +43,7 @@ public void setup() { filter.vaultRepo = vaultRepo; Mockito.doReturn(uriInfo).when(context).getUriInfo(); + Mockito.doReturn(securityContext).when(context).getSecurityContext(); } @Test @@ -173,6 +176,34 @@ public void testPass() throws NoSuchMethodException { Assertions.assertDoesNotThrow(() -> filter.filter(context)); } + @Nested + @DisplayName("if @VaultRole(onMissingVault = OnMissingVault.REQUIRE_REALM_ROLE)") + public class RequireRealmRole { + + @BeforeEach + public void setup() throws NoSuchMethodException { + Mockito.doReturn(NonExistingVault.class.getMethod("requireRealmRole")).when(resourceInfo).getResourceMethod(); + } + + @Test + @DisplayName("error 403 if user lacks realm role required by @VaultRole(realmRole = \"foobar\")") + public void testMissesRole() { + Mockito.doReturn(false).when(securityContext).isUserInRole("foobar"); + + Assertions.assertThrows(ForbiddenException.class, () -> filter.filter(context)); + } + + + @Test + @DisplayName("pass if user has realm role required by @VaultRole(realmRole = \"foobar\")") + public void testHasRole() { + Mockito.doReturn(true).when(securityContext).isUserInRole("foobar"); + + Assertions.assertDoesNotThrow(() -> filter.filter(context)); + } + + } + } /* @@ -194,6 +225,9 @@ public void notFound() {} @VaultRole(value = {VaultAccess.Role.OWNER}, onMissingVault = VaultRole.OnMissingVault.PASS) public void pass() {} + + @VaultRole(value = {VaultAccess.Role.OWNER}, onMissingVault = VaultRole.OnMissingVault.REQUIRE_REALM_ROLE, realmRole = "foobar") + public void requireRealmRole() {} }