From ded38360418ae0051b3e55a4f6884f027683b356 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Mon, 9 Oct 2023 15:56:50 +0100 Subject: [PATCH] Check semicolon during the OIDC scope to permission conversion --- .../io/quarkus/oidc/runtime/OidcUtils.java | 10 +++++++-- .../quarkus/oidc/runtime/OidcUtilsTest.java | 17 ++++++++++++++ .../it/keycloak/ServiceProtectedResource.java | 2 ++ .../it/keycloak/ServicePublicKeyTestCase.java | 22 +++++++++++++------ 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java index fa5164a21cdc5..d276832377c44 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java @@ -302,11 +302,17 @@ public Uni apply(Permission requiredPermission) { } } - private static Permission[] transformScopesToPermissions(Collection scopes) { + static Permission[] transformScopesToPermissions(Collection scopes) { final Permission[] permissions = new Permission[scopes.size()]; int i = 0; for (String scope : scopes) { - permissions[i++] = new StringPermission(scope); + int semicolonIndex = scope.indexOf(':'); + if (semicolonIndex > 0 && semicolonIndex < scope.length() - 1) { + permissions[i++] = new StringPermission(scope.substring(0, semicolonIndex), + scope.substring(semicolonIndex + 1)); + } else { + permissions[i++] = new StringPermission(scope); + } } return permissions; } diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java index 52105a54e974d..664ee777dfdfb 100644 --- a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java +++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java @@ -11,6 +11,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.security.Permission; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -664,6 +665,22 @@ public void testDecodeJwt() throws Exception { assertTrue(json.containsKey("jti")); } + @Test + public void testTransformScopeToPermission() throws Exception { + Permission[] perms = OidcUtils.transformScopesToPermissions( + List.of("read", "read:d", "read:", ":read")); + assertEquals(4, perms.length); + + assertEquals("read", perms[0].getName()); + assertNull(perms[0].getActions()); + assertEquals("read", perms[1].getName()); + assertEquals("d", perms[1].getActions()); + assertEquals("read:", perms[2].getName()); + assertNull(perms[2].getActions()); + assertEquals(":read", perms[3].getName()); + assertNull(perms[3].getActions()); + } + public static JsonObject read(InputStream input) throws IOException { try (BufferedReader buffer = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) { return new JsonObject(buffer.lines().collect(Collectors.joining("\n"))); diff --git a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/ServiceProtectedResource.java b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/ServiceProtectedResource.java index d7ca55fd2b1f0..bf462cace1b00 100644 --- a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/ServiceProtectedResource.java +++ b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/ServiceProtectedResource.java @@ -7,6 +7,7 @@ import org.eclipse.microprofile.jwt.JsonWebToken; import io.quarkus.security.Authenticated; +import io.quarkus.security.PermissionsAllowed; @Path("/service/tenant-public-key") @Authenticated @@ -16,6 +17,7 @@ public class ServiceProtectedResource { JsonWebToken accessToken; @GET + @PermissionsAllowed("read:data") public String getName() { return "tenant-public-key" + ":" + accessToken.getName(); } diff --git a/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/ServicePublicKeyTestCase.java b/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/ServicePublicKeyTestCase.java index e4500c1c98a6e..b358cf72f87ad 100644 --- a/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/ServicePublicKeyTestCase.java +++ b/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/ServicePublicKeyTestCase.java @@ -1,9 +1,10 @@ package io.quarkus.it.keycloak; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.io.IOException; import java.time.Instant; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import io.quarkus.test.junit.QuarkusTest; @@ -15,21 +16,28 @@ public class ServicePublicKeyTestCase { @Test - public void testAccessTokenInjection() throws IOException, InterruptedException { - String jwt = Jwt.claims().preferredUserName("alice").sign(); - Assertions.assertEquals("tenant-public-key:alice", RestAssured.given().auth() + public void testAccessTokenInjection() { + String jwt = Jwt.claim("scope", "read:data").preferredUserName("alice").sign(); + assertEquals("tenant-public-key:alice", RestAssured.given().auth() .oauth2(jwt) .get("/service/tenant-public-key").getBody().asString()); } @Test - public void testModifiedSignature() throws IOException, InterruptedException { + public void testAccessTokenInjection403() { + String jwt = Jwt.claim("scope", "read:doc").preferredUserName("alice").sign(); + RestAssured.given().auth().oauth2(jwt) + .get("/service/tenant-public-key").then().statusCode(403); + } + + @Test + public void testModifiedSignature() { String jwt = Jwt.claims().preferredUserName("alice").sign(); // the last section of the jwt token is a signature Response r = RestAssured.given().auth() .oauth2(jwt + "1") .get("/service/tenant-public-key"); - Assertions.assertEquals(401, r.getStatusCode()); + assertEquals(401, r.getStatusCode()); } @Test @@ -39,6 +47,6 @@ public void testExpiredToken() throws IOException, InterruptedException { Response r = RestAssured.given().auth() .oauth2(jwt) .get("/service/tenant-public-key"); - Assertions.assertEquals(401, r.getStatusCode()); + assertEquals(401, r.getStatusCode()); } }