From bf9da868aa25cd878ffc099a498ee1cefab0a70e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Sat, 25 Nov 2023 01:24:11 +0100 Subject: [PATCH] Allow to map token roles to deployment-specific SecurityIdentity roles --- ...ity-authorize-web-endpoints-reference.adoc | 13 + ...rity-oidc-bearer-token-authentication.adoc | 1 + ...ecurity-oidc-code-flow-authentication.adoc | 1 + ...SecurityPolicyGrantingPermissionsTest.java | 5 + .../HttpSecPolicyGrantingRolesTest.java | 245 ++++++++++++++++++ .../conf/http-roles-grant-config.properties | 28 ++ .../vertx/http/runtime/PolicyConfig.java | 9 + ...bstractPathMatchingHttpSecurityPolicy.java | 11 +- .../security/ImmutablePathMatcher.java | 6 +- .../RolesAllowedHttpSecurityPolicy.java | 114 ++++---- .../vertx/http/runtime/PathMatcherTest.java | 21 ++ 11 files changed, 401 insertions(+), 53 deletions(-) create mode 100644 extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/permission/HttpSecPolicyGrantingRolesTest.java create mode 100644 extensions/vertx-http/deployment/src/test/resources/conf/http-roles-grant-config.properties diff --git a/docs/src/main/asciidoc/security-authorize-web-endpoints-reference.adoc b/docs/src/main/asciidoc/security-authorize-web-endpoints-reference.adoc index 698225dd0f3a3..052c1f177365d 100644 --- a/docs/src/main/asciidoc/security-authorize-web-endpoints-reference.adoc +++ b/docs/src/main/asciidoc/security-authorize-web-endpoints-reference.adoc @@ -335,6 +335,19 @@ This configuration only impacts resources served from the fixed or static URL, ` For more information, see link:https://quarkus.io/blog/path-resolution-in-quarkus/[Path Resolution in Quarkus]. +[[map-security-identity-roles]] +=== Map `SecurityIdentity` roles + +Winning role-based policy can map the `SecurityIdentity` roles to the deployment specific roles. +These roles can later be used for endpoint authorization with the `@RolesAllowed` annotation. + +[source,properties] +---- +quarkus.http.auth.policy.admin-policy1.roles.admin=Admin1 <1> +quarkus.http.auth.permission.roles1.paths=/* +quarkus.http.auth.permission.roles1.policy=admin-policy1 +---- +<1> Map the `admin` role to `Admin1` role. The `SecurityIdentity` will have both `admin` and `Admin1` roles. [[standard-security-annotations]] == Authorization using annotations diff --git a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc index b6d397bbb3d72..a636c7dc392d8 100644 --- a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc +++ b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc @@ -114,6 +114,7 @@ If the token is opaque (binary) then a `scope` property from the remote token in If UserInfo is the source of the roles then set `quarkus.oidc.authentication.user-info-required=true` and `quarkus.oidc.roles.source=userinfo`, and if needed, `quarkus.oidc.roles.role-claim-path`. Additionally, a custom `SecurityIdentityAugmentor` can also be used to add the roles as documented in xref:security-customization.adoc#security-identity-customization[Security Identity Customization]. +You can also map `SecurityIdentity` roles created from token claims to deployment specific roles with the xref:security-authorize-web-endpoints-reference.adoc#map-security-identity-roles[HTTP Security policy]. [[token-scopes-and-security-identity-permissions]] === Token scopes And SecurityIdentity permissions diff --git a/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc b/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc index 7d65dd79d7d2e..96bafe7ccd471 100644 --- a/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc +++ b/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc @@ -438,6 +438,7 @@ If the access token contains the roles and this access token is not meant to be If UserInfo is the source of the roles then set `quarkus.oidc.roles.source=userinfo`, and if needed, `quarkus.oidc.roles.role-claim-path`. Additionally, a custom `SecurityIdentityAugmentor` can also be used to add the roles. For more information, see xref:security-customization.adoc#security-identity-customization[SecurityIdentity customization]. +You can also map `SecurityIdentity` roles created from token claims to deployment specific roles with the xref:security-authorize-web-endpoints-reference.adoc#map-security-identity-roles[HTTP Security policy]. === Ensuring validity of tokens and authentication data diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/permission/AbstractHttpSecurityPolicyGrantingPermissionsTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/permission/AbstractHttpSecurityPolicyGrantingPermissionsTest.java index d7719a9996b39..0a2001a36a09c 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/permission/AbstractHttpSecurityPolicyGrantingPermissionsTest.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/permission/AbstractHttpSecurityPolicyGrantingPermissionsTest.java @@ -305,6 +305,7 @@ interface AuthenticatedUser { enum AuthenticatedUserImpl implements AuthenticatedUser { ADMIN(AuthenticatedUserImpl::useAdminRole), + ROOT(AuthenticatedUserImpl::useRootRole), USER(AuthenticatedUserImpl::useUserRole), TEST(AuthenticatedUserImpl::useTestRole), TEST2(AuthenticatedUserImpl::useTest2Role); @@ -331,6 +332,10 @@ private static void useTest2Role() { TestIdentityController.resetRoles().add("test2", "test2", "test2"); } + private static void useRootRole() { + TestIdentityController.resetRoles().add("root", "root", "root", "Admin1"); + } + private static void useAdminRole() { TestIdentityController.resetRoles().add("admin", "admin", "admin"); } diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/permission/HttpSecPolicyGrantingRolesTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/permission/HttpSecPolicyGrantingRolesTest.java new file mode 100644 index 0000000000000..d281193a43143 --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/permission/HttpSecPolicyGrantingRolesTest.java @@ -0,0 +1,245 @@ +package io.quarkus.vertx.http.security.permission; + +import static io.quarkus.vertx.http.security.permission.AbstractHttpSecurityPolicyGrantingPermissionsTest.AuthenticatedUserImpl.ADMIN; +import static io.quarkus.vertx.http.security.permission.AbstractHttpSecurityPolicyGrantingPermissionsTest.AuthenticatedUserImpl.ROOT; +import static io.quarkus.vertx.http.security.permission.AbstractHttpSecurityPolicyGrantingPermissionsTest.AuthenticatedUserImpl.USER; + +import java.util.function.Supplier; + +import jakarta.annotation.security.RolesAllowed; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; + +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.security.AuthenticationFailedException; +import io.quarkus.security.ForbiddenException; +import io.quarkus.security.PermissionsAllowed; +import io.quarkus.security.UnauthorizedException; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.runtime.SecurityIdentityAssociation; +import io.quarkus.security.test.utils.TestIdentityController; +import io.quarkus.security.test.utils.TestIdentityProvider; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser; +import io.quarkus.vertx.http.security.CustomPermission; +import io.quarkus.vertx.http.security.CustomPermissionWithActions; +import io.quarkus.vertx.http.security.permission.AbstractHttpSecurityPolicyGrantingPermissionsTest.AuthenticatedUser; +import io.quarkus.vertx.http.security.permission.AbstractHttpSecurityPolicyGrantingPermissionsTest.AuthenticatedUserImpl; +import io.restassured.RestAssured; +import io.smallrye.mutiny.Uni; +import io.vertx.core.Handler; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; + +public class HttpSecPolicyGrantingRolesTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest().setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(TestIdentityController.class, TestIdentityProvider.class, RolesPathHandler.class, + CDIBean.class, CustomPermission.class, CustomPermissionWithActions.class, AuthenticatedUser.class, + AuthenticatedUserImpl.class) + .addAsResource("conf/http-roles-grant-config.properties", "application.properties")); + + @Test + public void mapRolesToRolesSecuredWithRolesAllowed() { + assertSuccess(ADMIN, "/test/new-admin-roles-blocking"); + assertSuccess(ADMIN, "/test/new-admin-roles"); + assertSuccess(ADMIN, "/test/new-admin-roles2"); + assertSuccess(ADMIN, "/test/old-admin-roles-blocking"); + assertSuccess(ADMIN, "/test/old-admin-roles"); + assertSuccess(ADMIN, "/test/multiple-new-roles-1"); + assertSuccess(ADMIN, "/test/multiple-new-roles-2"); + assertSuccess(ADMIN, "/test/multiple-new-roles-3"); + assertForbidden(USER, "/test/new-admin-roles-blocking"); + assertForbidden(USER, "/test/new-admin-roles"); + assertForbidden(USER, "/test/new-admin-roles2"); + assertForbidden(USER, "/test/old-admin-roles-blocking"); + assertForbidden(USER, "/test/old-admin-roles"); + assertSuccess(USER, "/test/multiple-new-roles-1"); + assertSuccess(USER, "/test/multiple-new-roles-2"); + assertSuccess(USER, "/test/multiple-new-roles-3"); + } + + @Test + public void mapRolesToRolesNoSecurityAnnotation() { + assertSuccess(ADMIN, "/test/roles-allowed-path"); + assertForbidden(USER, "/test/roles-allowed-path"); + assertForbidden(ROOT, "/test/roles-allowed-path"); + assertSuccess(ADMIN, "/test/granted-and-checked-by-policy"); + assertForbidden(USER, "/test/granted-and-checked-by-policy"); + assertSuccess(ROOT, "/test/granted-and-checked-by-policy"); + } + + @Test + public void mapRolesToBothPermissionsAndRoles() { + assertSuccess(ADMIN, "/test/roles-and-perms-1"); + assertSuccess(ADMIN, "/test/roles-and-perms-2"); + assertForbidden(USER, "/test/roles-and-perms-1"); + assertForbidden(USER, "/test/roles-and-perms-2"); + } + + @ApplicationScoped + public static class RolesPathHandler { + + @Inject + CDIBean cdiBean; + + public void setup(@Observes Router router) { + router.route("/test/new-admin-roles-blocking").blockingHandler(new RouteHandler(() -> { + cdiBean.newRolesBlocking(); + return Uni.createFrom().nullItem(); + })); + router.route("/test/new-admin-roles").handler(new RouteHandler(cdiBean::newRoles)); + router.route("/test/new-admin-roles2").handler(new RouteHandler(cdiBean::newRoles2)); + router.route("/test/old-admin-roles-blocking").blockingHandler(new RouteHandler(() -> { + cdiBean.oldRolesBlocking(); + return Uni.createFrom().nullItem(); + })); + router.route("/test/old-admin-roles").handler(new RouteHandler(cdiBean::oldRoles)); + router.route("/test/multiple-new-roles-1").handler(new RouteHandler(cdiBean::multipleNewRoles1)); + router.route("/test/multiple-new-roles-2").handler(new RouteHandler(cdiBean::multipleNewRoles2)); + router.route("/test/multiple-new-roles-3").handler(new RouteHandler(cdiBean::multipleNewRoles3)); + router.route("/test/roles-allowed-path").handler(new RouteHandler(cdiBean::checkAdmin1Role)); + router.route("/test/granted-and-checked-by-policy").handler(new RouteHandler(cdiBean::checkAdmin1Role)); + router.route("/test/roles-and-perms-1").handler(new RouteHandler(cdiBean::rolesAndPermissions1)); + router.route("/test/roles-and-perms-2").handler(new RouteHandler(cdiBean::rolesAndPermissions2)); + } + } + + private static final class RouteHandler implements Handler { + + private final Supplier> callService; + + private RouteHandler(Supplier> callService) { + this.callService = callService; + } + + @Override + public void handle(RoutingContext event) { + // activate context so that we can use CDI beans + Arc.container().requestContext().activate(); + // set identity used by security checks performed by standard security interceptors + QuarkusHttpUser user = (QuarkusHttpUser) event.user(); + Arc.container().instance(SecurityIdentityAssociation.class).get().setIdentity(user.getSecurityIdentity()); + + callService.get().subscribe().with(unused -> { + String ret = user.getSecurityIdentity().getPrincipal().getName() + + ":" + event.normalizedPath(); + event.response().end(ret); + }, throwable -> { + if (throwable instanceof UnauthorizedException) { + event.response().setStatusCode(401); + } else if (throwable instanceof ForbiddenException) { + event.response().setStatusCode(403); + } else { + event.response().setStatusCode(500); + } + event.end(); + }); + } + } + + private void assertSuccess(AuthenticatedUser user, String... paths) { + user.authenticate(); + for (var path : paths) { + RestAssured + .given() + .auth() + .basic(user.role(), user.role()) + .get(path) + .then() + .statusCode(200) + .body(Matchers.is(user.role() + ":" + path)); + } + } + + private void assertForbidden(AuthenticatedUser user, String... paths) { + user.authenticate(); + for (var path : paths) { + RestAssured + .given() + .auth() + .basic(user.role(), user.role()) + .get(path) + .then() + .statusCode(403); + } + } + + @ApplicationScoped + public static class CDIBean { + + @Inject + SecurityIdentity identity; + + @RolesAllowed("Admin1") + public void newRolesBlocking() { + // NOTHING TO DO + } + + @RolesAllowed("Admin1") + public Uni newRoles() { + return Uni.createFrom().nullItem(); + } + + @RolesAllowed("Admin2") + public Uni newRoles2() { + return Uni.createFrom().nullItem(); + } + + @RolesAllowed("admin") + public void oldRolesBlocking() { + // NOTHING TO DO + } + + @RolesAllowed("admin") + public Uni oldRoles() { + return Uni.createFrom().nullItem(); + } + + @RolesAllowed("Janet") + public Uni multipleNewRoles1() { + return Uni.createFrom().nullItem(); + } + + @RolesAllowed("Monica") + public Uni multipleNewRoles2() { + return Uni.createFrom().nullItem(); + } + + @RolesAllowed("Robin") + public Uni multipleNewRoles3() { + return Uni.createFrom().nullItem(); + } + + @RolesAllowed("Admin3") + public Uni rolesAndPermissions1() { + return Uni.createFrom().nullItem(); + } + + @PermissionsAllowed("jump") + public Uni rolesAndPermissions2() { + return Uni.createFrom().nullItem(); + } + + public Uni checkAdmin1Role() { + if (identity.hasRole("Admin1")) { + if (identity.getPrincipal().getName().equals("root")) { + if (identity.hasRole("sudo")) { + return Uni.createFrom().nullItem(); + } + } else { + return Uni.createFrom().nullItem(); + } + } + return Uni.createFrom().failure(AuthenticationFailedException::new); + } + } +} diff --git a/extensions/vertx-http/deployment/src/test/resources/conf/http-roles-grant-config.properties b/extensions/vertx-http/deployment/src/test/resources/conf/http-roles-grant-config.properties new file mode 100644 index 0000000000000..25d32756ad392 --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/resources/conf/http-roles-grant-config.properties @@ -0,0 +1,28 @@ +quarkus.http.auth.basic=true +quarkus.http.auth.policy.t1.roles.admin=Admin1 +quarkus.http.auth.permission.t1.paths=/* +quarkus.http.auth.permission.t1.policy=t1 +quarkus.http.auth.policy.t2.roles.admin=Admin2 +quarkus.http.auth.permission.t2.paths=/* +quarkus.http.auth.permission.t2.policy=t2 +quarkus.http.auth.policy.t3.roles.user=Janet,Robin +quarkus.http.auth.policy.t3.roles.admin=Janet,Robin +quarkus.http.auth.permission.t3.paths=/test/multiple-new-roles-1 +quarkus.http.auth.permission.t3.policy=t3 +quarkus.http.auth.policy.t4.roles.user=Monica,Robin +quarkus.http.auth.policy.t4.roles.admin=Monica,Robin +quarkus.http.auth.permission.t4.paths=/test/multiple-new-roles-2,/test/multiple-new-roles-3 +quarkus.http.auth.permission.t4.policy=t4 +quarkus.http.auth.policy.t5.roles-allowed=admin +quarkus.http.auth.policy.t5.roles.admin=Admin1 +quarkus.http.auth.permission.t5.paths=/test/roles-allowed-path +quarkus.http.auth.permission.t5.policy=t5 +quarkus.http.auth.policy.t6.roles-allowed=Admin1 +quarkus.http.auth.policy.t6.roles.admin=Admin1 +quarkus.http.auth.policy.t6.roles.root=sudo +quarkus.http.auth.permission.t6.paths=/test/granted-and-checked-by-policy +quarkus.http.auth.permission.t6.policy=t6 +quarkus.http.auth.policy.t7.roles.admin=Admin3 +quarkus.http.auth.policy.t7.permissions.admin=jump +quarkus.http.auth.permission.t7.paths=/test/roles-and-perms-1,/test/roles-and-perms-2 +quarkus.http.auth.permission.t7.policy=t7 \ No newline at end of file diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PolicyConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PolicyConfig.java index 6977b99f08770..be472e3ea47b7 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PolicyConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PolicyConfig.java @@ -21,6 +21,15 @@ public class PolicyConfig { @ConvertWith(TrimmedStringConverter.class) public List rolesAllowed; + /** + * Add roles granted to the `SecurityIdentity` based on the roles that the `SecurityIdentity` already have. + * For example, the Quarkus OIDC extension can map roles from the verified JWT access token, and you may want + * to remap them to a deployment specific roles. + */ + @ConfigDocMapKey("role1") + @ConfigItem + public Map> roles; + /** * Permissions granted to the `SecurityIdentity` if this policy is applied successfully * (the policy allows request to proceed) and the authenticated request has required role. diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/AbstractPathMatchingHttpSecurityPolicy.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/AbstractPathMatchingHttpSecurityPolicy.java index 3371e6c365162..a1052fe8649d0 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/AbstractPathMatchingHttpSecurityPolicy.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/AbstractPathMatchingHttpSecurityPolicy.java @@ -162,11 +162,12 @@ private static Map toNamedHttpSecPolicies(Map e : rolePolicies.entrySet()) { - PolicyConfig policyConfig = e.getValue(); + final PolicyConfig policyConfig = e.getValue(); + final Map> roleToPermissions; if (policyConfig.permissions.isEmpty()) { - namedPolicies.put(e.getKey(), new RolesAllowedHttpSecurityPolicy(policyConfig.rolesAllowed)); + roleToPermissions = null; } else { - final Map> roleToPermissions = new HashMap<>(); + roleToPermissions = new HashMap<>(); for (Map.Entry> roleToPermissionStr : policyConfig.permissions.entrySet()) { // collect permission actions @@ -190,9 +191,9 @@ private static Map toNamedHttpSecPolicies(Map build() { if (p.prefixPathHandler != null) { handler = p.prefixPathHandler; if (STRING_PATH_SEPARATOR.equals(p.path)) { - defaultHandler = p.prefixPathHandler; + if (defaultHandler == null) { + defaultHandler = p.prefixPathHandler; + } else { + handlerAccumulator.accept(defaultHandler, p.prefixPathHandler); + } } } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/RolesAllowedHttpSecurityPolicy.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/RolesAllowedHttpSecurityPolicy.java index 4b96c48c8786f..52061b6c41184 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/RolesAllowedHttpSecurityPolicy.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/RolesAllowedHttpSecurityPolicy.java @@ -17,34 +17,30 @@ * permission checker that handles role based permissions */ public class RolesAllowedHttpSecurityPolicy implements HttpSecurityPolicy { - private List rolesAllowed; + private static final String AUTHENTICATED = "**"; + private final String[] rolesAllowed; private final boolean grantPermissions; + private final boolean grantRoles; private final Map> roleToPermissions; - - public RolesAllowedHttpSecurityPolicy(List rolesAllowed) { - this.rolesAllowed = rolesAllowed; - this.grantPermissions = false; - this.roleToPermissions = null; - } - - public RolesAllowedHttpSecurityPolicy() { - this.grantPermissions = false; - this.roleToPermissions = null; - } - - public RolesAllowedHttpSecurityPolicy(List rolesAllowed, Map> roleToPermissions) { - this.rolesAllowed = rolesAllowed; - this.grantPermissions = true; - this.roleToPermissions = roleToPermissions; - } - - public List getRolesAllowed() { - return rolesAllowed; - } - - public RolesAllowedHttpSecurityPolicy setRolesAllowed(List rolesAllowed) { - this.rolesAllowed = rolesAllowed; - return this; + private final Map> roleToRoles; + + public RolesAllowedHttpSecurityPolicy(List rolesAllowed, Map> roleToPermissions, + Map> roleToRoles) { + if (roleToPermissions != null && !roleToPermissions.isEmpty()) { + this.grantPermissions = true; + this.roleToPermissions = Map.copyOf(roleToPermissions); + } else { + this.grantPermissions = false; + this.roleToPermissions = null; + } + if (roleToRoles != null && !roleToRoles.isEmpty()) { + this.grantRoles = true; + this.roleToRoles = Map.copyOf(roleToRoles); + } else { + this.grantRoles = false; + this.roleToRoles = null; + } + this.rolesAllowed = rolesAllowed.toArray(String[]::new); } @Override @@ -53,12 +49,19 @@ public Uni checkPermission(RoutingContext request, Uni() { @Override public CheckResult apply(SecurityIdentity securityIdentity) { - for (String i : rolesAllowed) { - if (securityIdentity.hasRole(i) || ("**".equals(i) && !securityIdentity.isAnonymous())) { - if (grantPermissions) { - // permit access and add augment security identity with additional permissions - return grantPermissions(securityIdentity); + if (grantPermissions || grantRoles) { + SecurityIdentity augmented = augmentIdentity(securityIdentity); + if (augmented != null) { + for (String i : rolesAllowed) { + if (augmented.hasRole(i) || (AUTHENTICATED.equals(i) && !augmented.isAnonymous())) { + return new CheckResult(true, augmented); + } } + return CheckResult.DENY; + } + } + for (String i : rolesAllowed) { + if (securityIdentity.hasRole(i) || (AUTHENTICATED.equals(i) && !securityIdentity.isAnonymous())) { return CheckResult.PERMIT; } } @@ -67,23 +70,36 @@ public CheckResult apply(SecurityIdentity securityIdentity) { }); } - private CheckResult grantPermissions(SecurityIdentity securityIdentity) { + private SecurityIdentity augmentIdentity(SecurityIdentity securityIdentity) { Set roles = securityIdentity.getRoles(); if (roles != null && !roles.isEmpty()) { - Set permissions = new HashSet<>(); + Set permissions = grantPermissions ? new HashSet<>() : null; + Set newRoles = grantRoles ? new HashSet<>() : null; for (String role : roles) { - if (roleToPermissions.containsKey(role)) { - permissions.addAll(roleToPermissions.get(role)); + if (grantPermissions) { + if (roleToPermissions.containsKey(role)) { + permissions.addAll(roleToPermissions.get(role)); + } + } + if (grantRoles) { + if (roleToRoles.containsKey(role)) { + newRoles.addAll(roleToRoles.get(role)); + } } } - if (!permissions.isEmpty()) { - return new CheckResult(true, augmentIdentity(securityIdentity, permissions)); + boolean addPerms = grantPermissions && !permissions.isEmpty(); + if (grantRoles && !newRoles.isEmpty()) { + newRoles.addAll(roles); + return augmentIdentity(securityIdentity, permissions, Set.copyOf(newRoles), addPerms); + } else if (addPerms) { + return augmentIdentity(securityIdentity, permissions, roles, true); } } - return CheckResult.PERMIT; + return null; } - private static SecurityIdentity augmentIdentity(SecurityIdentity securityIdentity, Set permissions) { + private static SecurityIdentity augmentIdentity(SecurityIdentity securityIdentity, Set permissions, + Set roles, boolean addPerms) { return new SecurityIdentity() { @Override public Principal getPrincipal() { @@ -97,12 +113,12 @@ public boolean isAnonymous() { @Override public Set getRoles() { - return securityIdentity.getRoles(); + return roles; } @Override public boolean hasRole(String s) { - return securityIdentity.hasRole(s); + return roles.contains(s); } @Override @@ -127,9 +143,11 @@ public Map getAttributes() { @Override public Uni checkPermission(Permission requiredPermission) { - for (Permission possessedPermission : permissions) { - if (possessedPermission.implies(requiredPermission)) { - return Uni.createFrom().item(true); + if (addPerms) { + for (Permission possessedPermission : permissions) { + if (possessedPermission.implies(requiredPermission)) { + return Uni.createFrom().item(true); + } } } @@ -138,9 +156,11 @@ public Uni checkPermission(Permission requiredPermission) { @Override public boolean checkPermissionBlocking(Permission requiredPermission) { - for (Permission possessedPermission : permissions) { - if (possessedPermission.implies(requiredPermission)) { - return true; + if (addPerms) { + for (Permission possessedPermission : permissions) { + if (possessedPermission.implies(requiredPermission)) { + return true; + } } } diff --git a/extensions/vertx-http/runtime/src/test/java/io/quarkus/vertx/http/runtime/PathMatcherTest.java b/extensions/vertx-http/runtime/src/test/java/io/quarkus/vertx/http/runtime/PathMatcherTest.java index 9cc89a3e3bd32..dfbfe2add67ed 100644 --- a/extensions/vertx-http/runtime/src/test/java/io/quarkus/vertx/http/runtime/PathMatcherTest.java +++ b/extensions/vertx-http/runtime/src/test/java/io/quarkus/vertx/http/runtime/PathMatcherTest.java @@ -340,6 +340,27 @@ public void testExactPathHandlerMerging() { assertEquals(1, handler.size()); } + @Test + public void testDefaultHandlerMerging() { + List handler1 = new ArrayList<>(); + handler1.add("Neo"); + List handler2 = new ArrayList<>(); + handler2.add("Trinity"); + List handler3 = new ArrayList<>(); + handler3.add("Morpheus"); + var matcher = ImmutablePathMatcher.> builder().handlerAccumulator(List::addAll) + .addPath("/*", handler1).addPath("/*", handler2) + .addPath("/", handler3).build(); + var handler = matcher.match("/default-path-handler").getValue(); + assertNotNull(handler); + assertTrue(handler.contains("Neo")); + assertTrue(handler.contains("Trinity")); + assertEquals(2, handler.size()); + handler = matcher.match("/").getValue(); + assertNotNull(handler); + assertEquals(1, handler.size()); + } + @Test public void testPrefixPathHandlerMerging() { List handler1 = new ArrayList<>();