From eb50688fd0de17a513d3e54211f411a0176319c1 Mon Sep 17 00:00:00 2001 From: Enrico Vianello Date: Fri, 4 Oct 2024 12:14:10 +0200 Subject: [PATCH] Fix creation of non-existent parent directory authZ with WLCG scopes --- .../WlcgStructuredPathAuthorizationPdp.java | 21 ++- .../util/StructuredPathScopeMatcher.java | 12 +- .../authz/pdp/ScopePathAuthzPdpTests.java | 145 +++++++++++------- 3 files changed, 113 insertions(+), 65 deletions(-) diff --git a/src/main/java/org/italiangrid/storm/webdav/authz/pdp/WlcgStructuredPathAuthorizationPdp.java b/src/main/java/org/italiangrid/storm/webdav/authz/pdp/WlcgStructuredPathAuthorizationPdp.java index 5718da45..6a26c2ae 100644 --- a/src/main/java/org/italiangrid/storm/webdav/authz/pdp/WlcgStructuredPathAuthorizationPdp.java +++ b/src/main/java/org/italiangrid/storm/webdav/authz/pdp/WlcgStructuredPathAuthorizationPdp.java @@ -138,7 +138,7 @@ boolean filterMatcherByRequest(HttpServletRequest request, String method, if (requestedResourceExists) { return m.getPrefix().equals(STORAGE_MODIFY); } - return m.getPrefix().equals(STORAGE_CREATE); + return m.getPrefix().equals(STORAGE_CREATE) || m.getPrefix().equals(STORAGE_MODIFY); } if (MODIFY_METHODS.contains(method)) { return m.getPrefix().equals(STORAGE_MODIFY); @@ -149,9 +149,9 @@ boolean filterMatcherByRequest(HttpServletRequest request, String method, if (requestedResourceExists) { return m.getPrefix().equals(STORAGE_MODIFY); } - return m.getPrefix().equals(STORAGE_CREATE); + return m.getPrefix().equals(STORAGE_CREATE) || m.getPrefix().equals(STORAGE_MODIFY); } - return m.getPrefix().equals(STORAGE_READ); + return m.getPrefix().equals(STORAGE_READ) || m.getPrefix().equals(STORAGE_STAGE); } @@ -212,10 +212,17 @@ public PathAuthorizationResult authorizeRequest(PathAuthorizationRequest authzRe final boolean requestedResourceExists = pathResolver.pathExists(requestPath); final String saPath = getStorageAreaPath(requestPath, sa); - scopeMatchers = scopeMatchers.stream() - .filter(m -> filterMatcherByRequest(request, method, m, requestedResourceExists)) - .filter(m -> m.matchesPath(saPath)) - .collect(toList()); + if ("MKCOL".equals(method)) { + scopeMatchers = scopeMatchers.stream() + .filter(m -> filterMatcherByRequest(request, method, m, requestedResourceExists)) + .filter(m -> m.matchesPathIncludingParents(saPath)) + .collect(toList()); + } else { + scopeMatchers = scopeMatchers.stream() + .filter(m -> filterMatcherByRequest(request, method, m, requestedResourceExists)) + .filter(m -> m.matchesPath(saPath)) + .collect(toList()); + } if (scopeMatchers.isEmpty()) { return deny(ERROR_INSUFFICIENT_TOKEN_SCOPE); diff --git a/src/main/java/org/italiangrid/storm/webdav/authz/util/StructuredPathScopeMatcher.java b/src/main/java/org/italiangrid/storm/webdav/authz/util/StructuredPathScopeMatcher.java index 1172db03..853519b0 100644 --- a/src/main/java/org/italiangrid/storm/webdav/authz/util/StructuredPathScopeMatcher.java +++ b/src/main/java/org/italiangrid/storm/webdav/authz/util/StructuredPathScopeMatcher.java @@ -19,6 +19,7 @@ import static com.google.common.base.Strings.isNullOrEmpty; import static java.util.Objects.nonNull; +import java.nio.file.Path; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -37,7 +38,7 @@ public class StructuredPathScopeMatcher implements ScopeMatcher { private static final String SEP_STR = SEP.toString(); private final String prefix; - private final String path; + private final Path path; private final Pattern prefixMatchPattern; private final Pattern pathMatchPattern; @@ -45,7 +46,7 @@ public class StructuredPathScopeMatcher implements ScopeMatcher { private StructuredPathScopeMatcher(String prefix, String path) { this.prefix = prefix; - this.path = path; + this.path = Path.of(path); final String prefixMatchRegexp = String.format("^%s%c", prefix, SEP); prefixMatchPattern = Pattern.compile(prefixMatchRegexp); @@ -75,6 +76,11 @@ public boolean matchesScope(String scope) { public boolean matchesPath(String path) { return pathMatchPattern.matcher(path).matches(); } + + public boolean matchesPathIncludingParents(String path) { + Path targetPath = Path.of(path); + return this.path.startsWith(targetPath) || matchesPath(path); + } public static StructuredPathScopeMatcher fromString(String scope) { final int sepIndex = scope.indexOf(SEP); @@ -135,7 +141,7 @@ public String getPrefix() { } public String getPath() { - return path; + return path.toString(); } } diff --git a/src/test/java/org/italiangrid/storm/webdav/test/authz/pdp/ScopePathAuthzPdpTests.java b/src/test/java/org/italiangrid/storm/webdav/test/authz/pdp/ScopePathAuthzPdpTests.java index 18b3dfe6..47a6eba3 100644 --- a/src/test/java/org/italiangrid/storm/webdav/test/authz/pdp/ScopePathAuthzPdpTests.java +++ b/src/test/java/org/italiangrid/storm/webdav/test/authz/pdp/ScopePathAuthzPdpTests.java @@ -21,6 +21,7 @@ import static org.italiangrid.storm.webdav.authz.pdp.PathAuthorizationRequest.newAuthorizationRequest; import static org.italiangrid.storm.webdav.authz.pdp.WlcgStructuredPathAuthorizationPdp.SCOPE_CLAIM; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -95,6 +96,7 @@ public void setup() throws MalformedURLException { lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("storage.read:/"); lenient().when(request.getServletPath()).thenReturn("/"); lenient().when(request.getPathInfo()).thenReturn("test/example"); + lenient().when(sa.rootPath()).thenReturn("/storage"); lenient().when(sa.accessPoints()).thenReturn(Lists.newArrayList("/test")); lenient().when(sa.orgs()).thenReturn(Sets.newHashSet("https://issuer.example")); lenient().when(pathResolver.resolveStorageArea("/test/example")).thenReturn(sa); @@ -111,7 +113,7 @@ void invalidAuthentication() { @Test void noScopeClaimYeldsIndeterminate() { - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn(null); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn(null); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.INDETERMINATE)); @@ -122,7 +124,7 @@ void noScopeClaimYeldsIndeterminate() { @Test void noSaYeldsIndeterminate() { - when(pathResolver.resolveStorageArea("/test/example")).thenReturn(null); + lenient().when(pathResolver.resolveStorageArea("/test/example")).thenReturn(null); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.INDETERMINATE)); @@ -132,7 +134,7 @@ void noSaYeldsIndeterminate() { @Test void noStorageScopesYeldsDeny() { - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid profile storage.read"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid profile storage.read"); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); @@ -144,10 +146,10 @@ void noStorageScopesYeldsDeny() { @Test void noStorageScopesYeldsDenyForCatchallMethods() { - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid profile"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid profile"); for (String m : CATCHALL_METHODS) { - when(request.getMethod()).thenReturn(m); + lenient().when(request.getMethod()).thenReturn(m); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); @@ -159,37 +161,37 @@ void noStorageScopesYeldsDenyForCatchallMethods() { @Test void catchallMethodsRequestsAtLeastOneStorageScope() { - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.modify:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.modify:/"); for (String m : CATCHALL_METHODS) { - when(request.getMethod()).thenReturn(m); + lenient().when(request.getMethod()).thenReturn(m); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.PERMIT)); } - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/"); for (String m : CATCHALL_METHODS) { - when(request.getMethod()).thenReturn(m); + lenient().when(request.getMethod()).thenReturn(m); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.PERMIT)); } - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.create:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.create:/"); for (String m : CATCHALL_METHODS) { - when(request.getMethod()).thenReturn(m); + lenient().when(request.getMethod()).thenReturn(m); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.PERMIT)); } - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.stage:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.stage:/"); for (String m : CATCHALL_METHODS) { - when(request.getMethod()).thenReturn(m); + lenient().when(request.getMethod()).thenReturn(m); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.PERMIT)); @@ -198,10 +200,10 @@ void catchallMethodsRequestsAtLeastOneStorageScope() { @Test void readMethodsRequestsRequireStorageReadOrStage() { - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.modify:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.modify:/"); for (String m : READ_METHODS) { - when(request.getMethod()).thenReturn(m); + lenient().when(request.getMethod()).thenReturn(m); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.DENY)); @@ -209,19 +211,19 @@ void readMethodsRequestsRequireStorageReadOrStage() { assertThat(result.getMessage().get(), containsString("Insufficient token scope")); } - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/"); for (String m : READ_METHODS) { - when(request.getMethod()).thenReturn(m); + lenient().when(request.getMethod()).thenReturn(m); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.PERMIT)); } - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.stage:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.stage:/"); for (String m : READ_METHODS) { - when(request.getMethod()).thenReturn(m); + lenient().when(request.getMethod()).thenReturn(m); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.PERMIT)); @@ -230,10 +232,10 @@ void readMethodsRequestsRequireStorageReadOrStage() { @Test void replaceMethodsRequestsRequireStorageModifyOrCreate() { - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/"); for (String m : REPLACE_METHODS) { - when(request.getMethod()).thenReturn(m); + lenient().when(request.getMethod()).thenReturn(m); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.DENY)); @@ -241,11 +243,12 @@ void replaceMethodsRequestsRequireStorageModifyOrCreate() { assertThat(result.getMessage().get(), containsString("Insufficient token scope")); } - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/ storage.create:/"); - when(pathResolver.pathExists("/test/example")).thenReturn(true); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)) + .thenReturn("openid storage.read:/ storage.create:/"); + lenient().when(pathResolver.pathExists("/test/example")).thenReturn(true); for (String m : REPLACE_METHODS) { - when(request.getMethod()).thenReturn(m); + lenient().when(request.getMethod()).thenReturn(m); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.DENY)); @@ -253,7 +256,7 @@ void replaceMethodsRequestsRequireStorageModifyOrCreate() { assertThat(result.getMessage().get(), containsString("Insufficient token scope")); } - when(pathResolver.pathExists("/test/example")).thenReturn(false); + lenient().when(pathResolver.pathExists("/test/example")).thenReturn(false); for (String m : REPLACE_METHODS) { when(request.getMethod()).thenReturn(m); @@ -262,10 +265,10 @@ void replaceMethodsRequestsRequireStorageModifyOrCreate() { assertThat(result.getDecision(), is(Decision.PERMIT)); } - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.modify:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.modify:/"); for (String m : REPLACE_METHODS) { - when(request.getMethod()).thenReturn(m); + lenient().when(request.getMethod()).thenReturn(m); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.PERMIT)); @@ -274,10 +277,11 @@ void replaceMethodsRequestsRequireStorageModifyOrCreate() { @Test void modifyMethodsRequestsRequireStorageModifyOrCreate() { - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/ storage.create:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)) + .thenReturn("openid storage.read:/ storage.create:/"); for (String m : MODIFY_METHODS) { - when(request.getMethod()).thenReturn(m); + lenient().when(request.getMethod()).thenReturn(m); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.DENY)); @@ -285,10 +289,11 @@ void modifyMethodsRequestsRequireStorageModifyOrCreate() { assertThat(result.getMessage().get(), containsString("Insufficient token scope")); } - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/ storage.modify:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)) + .thenReturn("openid storage.read:/ storage.modify:/"); for (String m : MODIFY_METHODS) { - when(request.getMethod()).thenReturn(m); + lenient().when(request.getMethod()).thenReturn(m); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.PERMIT)); @@ -298,8 +303,8 @@ void modifyMethodsRequestsRequireStorageModifyOrCreate() { @Test void testLocalCopyRequiresStorageCreateOrModify() { - when(request.getMethod()).thenReturn(COPY_METHOD); - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.modify:/"); + lenient().when(request.getMethod()).thenReturn(COPY_METHOD); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.modify:/"); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); @@ -307,17 +312,18 @@ void testLocalCopyRequiresStorageCreateOrModify() { assertThat(result.getMessage().isPresent(), is(true)); assertThat(result.getMessage().get(), containsString("Insufficient token scope")); - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/ storage.write:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)) + .thenReturn("openid storage.read:/ storage.write:/"); result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.PERMIT)); } @Test void testPullTpcRequiresCreateOrModify() { - when(request.getMethod()).thenReturn(COPY_METHOD); - when(request.getHeader("Source")).thenReturn("https://remote.example/test/example"); - when(pathResolver.pathExists("/test/example")).thenReturn(true); - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/"); + lenient().when(request.getMethod()).thenReturn(COPY_METHOD); + lenient().when(request.getHeader("Source")).thenReturn("https://remote.example/test/example"); + lenient().when(pathResolver.pathExists("/test/example")).thenReturn(true); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/"); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); @@ -325,25 +331,26 @@ void testPullTpcRequiresCreateOrModify() { assertThat(result.getMessage().isPresent(), is(true)); assertThat(result.getMessage().get(), containsString("Insufficient token scope")); - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.create:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.create:/"); result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.DENY)); assertThat(result.getMessage().isPresent(), is(true)); assertThat(result.getMessage().get(), containsString("Insufficient token scope")); - when(pathResolver.pathExists("/test/example")).thenReturn(false); + lenient().when(pathResolver.pathExists("/test/example")).thenReturn(false); result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.PERMIT)); - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.modify:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.modify:/"); result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.PERMIT)); } @Test void testPushTpcRequiresRead() { - when(request.getMethod()).thenReturn(COPY_METHOD); - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.create:/ storage.modify:/"); + lenient().when(request.getMethod()).thenReturn(COPY_METHOD); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)) + .thenReturn("openid storage.create:/ storage.modify:/"); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); @@ -351,15 +358,16 @@ void testPushTpcRequiresRead() { assertThat(result.getMessage().isPresent(), is(true)); assertThat(result.getMessage().get(), containsString("Insufficient token scope")); - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/"); result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.PERMIT)); } @Test void testMoveRequiresModify() { - when(request.getMethod()).thenReturn(MOVE_METHOD); - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/ storage.create:/"); + lenient().when(request.getMethod()).thenReturn(MOVE_METHOD); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)) + .thenReturn("openid storage.read:/ storage.create:/"); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); @@ -367,15 +375,16 @@ void testMoveRequiresModify() { assertThat(result.getMessage().isPresent(), is(true)); assertThat(result.getMessage().get(), containsString("Insufficient token scope")); - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.modify:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.modify:/"); result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.PERMIT)); } @Test void testModifyImpliesCreate() { - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/ storage.modify:/"); - when(pathResolver.pathExists("/test/example")).thenReturn(false); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)) + .thenReturn("openid storage.read:/ storage.modify:/"); + lenient().when(pathResolver.pathExists("/test/example")).thenReturn(false); for (String m : REPLACE_METHODS) { when(request.getMethod()).thenReturn(m); @@ -387,7 +396,7 @@ void testModifyImpliesCreate() { @Test void testUnsupportedMethod() { - when(request.getMethod()).thenReturn("TRACE"); + lenient().when(request.getMethod()).thenReturn("TRACE"); assertThrows(IllegalArgumentException.class, () -> { pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); }); @@ -395,15 +404,15 @@ void testUnsupportedMethod() { @Test void testPathAuthzIsEnforced() { - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/subfolder"); - when(request.getMethod()).thenReturn("GET"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/subfolder"); + lenient().when(request.getMethod()).thenReturn("GET"); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.DENY)); assertThat(result.getMessage().isPresent(), is(true)); assertThat(result.getMessage().get(), containsString("Insufficient token scope")); - when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)).thenReturn("openid storage.read:/"); result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.PERMIT)); @@ -411,12 +420,38 @@ void testPathAuthzIsEnforced() { @Test void issuerChecksAreEnforced() throws Exception { - when(jwt.getIssuer()).thenReturn(new URL("https://unknown.example")); - when(request.getMethod()).thenReturn("GET"); + lenient().when(jwt.getIssuer()).thenReturn(new URL("https://unknown.example")); + lenient().when(request.getMethod()).thenReturn("GET"); PathAuthorizationResult result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); assertThat(result.getDecision(), is(Decision.DENY)); assertThat(result.getMessage().isPresent(), is(true)); assertThat(result.getMessage().get(), containsString("Unknown token issuer")); } + + @Test + void parentDirCreationIsAllowedWithStorageCreateOrModify() { + + lenient().when(pathResolver.resolveStorageArea(anyString())).thenReturn(sa); + lenient().when(request.getPathInfo()).thenReturn("test/dir/subdir"); + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)) + .thenReturn("openid storage.create:/dir/subdir"); + lenient().when(request.getMethod()).thenReturn("MKCOL"); + PathAuthorizationResult result = + pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); + assertThat(result.getDecision(), is(Decision.PERMIT)); + + lenient().when(request.getPathInfo()).thenReturn("test/dir"); + result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); + assertThat(result.getDecision(), is(Decision.PERMIT)); + + lenient().when(jwt.getClaimAsString(SCOPE_CLAIM)) + .thenReturn("openid storage.modify:/dir/subdir"); + result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); + assertThat(result.getDecision(), is(Decision.PERMIT)); + + lenient().when(request.getPathInfo()).thenReturn("test/dir"); + result = pdp.authorizeRequest(newAuthorizationRequest(request, jwtAuth)); + assertThat(result.getDecision(), is(Decision.PERMIT)); + } }