diff --git a/docs/src/main/asciidoc/security-testing.adoc b/docs/src/main/asciidoc/security-testing.adoc index 0b756e24c1747..bc79dc86e6571 100644 --- a/docs/src/main/asciidoc/security-testing.adoc +++ b/docs/src/main/asciidoc/security-testing.adoc @@ -93,6 +93,24 @@ identity to be present. See xref:security-oidc-bearer-token-authentication.adoc#integration-testing-security-annotation[OpenID Connect Bearer Token Integration testing], xref:security-oidc-code-flow-authentication.adoc#integration-testing-security-annotation[OpenID Connect Authorization Code Flow Integration testing] and xref:security-jwt.adoc#integration-testing-security-annotation[SmallRye JWT Integration testing] for more details about testing the endpoint code which depends on the injected `JsonWebToken`. +Additionally, you can specify attributes for the identity, perhaps custom items that were added with identity augmentation: + +[source,java] +---- +@Inject +SecurityIdentity identity; + +@Test +@TestSecurity(user = "testUser", "roles = {"admin, "user"}, attributes = { + @SecurityAttribute(key = "answer", value = "42", type = AttributeType.LONG) } +void someTestMethod() { + Long answer = identity.getAttribute("answer"); +... +} +---- + +This will run the test with an identity with an attribute of type `Long` named `answer`. + [WARNING] ==== The feature is only available for `@QuarkusTest` and will **not** work on a `@QuarkusIntegrationTest`. diff --git a/integration-tests/elytron-resteasy/src/test/java/io/quarkus/it/resteasy/elytron/TestSecurityTestCase.java b/integration-tests/elytron-resteasy/src/test/java/io/quarkus/it/resteasy/elytron/TestSecurityTestCase.java index 4237cb4774f65..3616a21458374 100644 --- a/integration-tests/elytron-resteasy/src/test/java/io/quarkus/it/resteasy/elytron/TestSecurityTestCase.java +++ b/integration-tests/elytron-resteasy/src/test/java/io/quarkus/it/resteasy/elytron/TestSecurityTestCase.java @@ -1,7 +1,10 @@ package io.quarkus.it.resteasy.elytron; import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.params.provider.Arguments.arguments; import java.util.stream.Stream; @@ -13,6 +16,7 @@ import org.junit.jupiter.params.provider.ValueSource; import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.security.AttributeType; import io.quarkus.test.security.SecurityAttribute; import io.quarkus.test.security.TestSecurity; @@ -94,6 +98,50 @@ void testAttributes() { .body(is("foo=bar")); } + @Test + @TestSecurity(user = "testUser", roles = "user", attributes = { + @SecurityAttribute(key = "foo", value = "9223372036854775807", type = AttributeType.LONG) }) + void testLongAttributes() { + given() + .when() + .get("/attributes") + .then() + .statusCode(200) + .body(is("foo=" + Long.MAX_VALUE)); + } + + @Test + @TestSecurity(user = "testUser", roles = "user", attributes = { + @SecurityAttribute(key = "foo", value = "[\"A\",\"B\",\"C\"]", type = AttributeType.JSON_ARRAY) }) + void testJsonArrayAttributes() { + given() + .when() + .get("/attributes") + .then() + .statusCode(200) + .body(is("foo=[\"A\",\"B\",\"C\"]")); + } + + @Test + @TestSecurity(user = "testUser", roles = "user", attributes = { + @SecurityAttribute(key = "foo", value = "\"A\",\"B\",\"C\"", type = AttributeType.STRING_SET) }) + void testStringSetAttributes() { + given() + .when() + .get("/attributes") + .then() + .statusCode(200) + .body(startsWith("foo=[")) + .and() + .body(endsWith("]")) + .and() + .body(containsString("\"A\"")) + .and() + .body(containsString("\"B\"")) + .and() + .body(containsString("\"C\"")); + } + static Stream arrayParams() { return Stream.of( arguments(new int[] { 1, 2 }, new String[] { "hello", "world" })); diff --git a/test-framework/security/pom.xml b/test-framework/security/pom.xml index 56420535a7f2a..5eeec97ddb26e 100644 --- a/test-framework/security/pom.xml +++ b/test-framework/security/pom.xml @@ -32,6 +32,12 @@ junit-jupiter compile + + io.quarkus + quarkus-jsonp + compile + true + diff --git a/test-framework/security/src/main/java/io/quarkus/test/security/AttributeType.java b/test-framework/security/src/main/java/io/quarkus/test/security/AttributeType.java new file mode 100644 index 0000000000000..62010c37f8e33 --- /dev/null +++ b/test-framework/security/src/main/java/io/quarkus/test/security/AttributeType.java @@ -0,0 +1,69 @@ +package io.quarkus.test.security; + +import java.io.StringReader; +import java.util.Set; + +import jakarta.json.Json; +import jakarta.json.JsonReader; + +public enum AttributeType { + LONG { + @Override + Object convert(String value) { + return Long.valueOf(value); + } + }, + INTEGER { + @Override + Object convert(String value) { + return Integer.valueOf(value); + } + }, + BOOLEAN { + @Override + Object convert(String value) { + return Boolean.valueOf(value); + } + }, + STRING { + @Override + Object convert(String value) { + return value; + } + }, + STRING_SET { + /** + * Returns a Set of String values, parsed from the given value. + * + * @param value a comma separated list of values + */ + @Override + Object convert(String value) { + return Set.of(value.split(",")); + } + }, + JSON_ARRAY { + @Override + Object convert(String value) { + try (JsonReader reader = Json.createReader(new StringReader(value))) { + return reader.readArray(); + } + } + }, + JSON_OBJECT { + @Override + Object convert(String value) { + try (JsonReader reader = Json.createReader(new StringReader(value))) { + return reader.readObject(); + } + } + }, + DEFAULT { + @Override + Object convert(String value) { + return value; + } + }; + + abstract Object convert(String value); +} diff --git a/test-framework/security/src/main/java/io/quarkus/test/security/QuarkusSecurityTestExtension.java b/test-framework/security/src/main/java/io/quarkus/test/security/QuarkusSecurityTestExtension.java index 0780c0d9584b8..44a0bc2750278 100644 --- a/test-framework/security/src/main/java/io/quarkus/test/security/QuarkusSecurityTestExtension.java +++ b/test-framework/security/src/main/java/io/quarkus/test/security/QuarkusSecurityTestExtension.java @@ -60,7 +60,7 @@ public void beforeEach(QuarkusTestMethodContext context) { if (testSecurity.attributes() != null) { user.addAttributes(Arrays.stream(testSecurity.attributes()) - .collect(Collectors.toMap(s -> s.key(), s -> s.value()))); + .collect(Collectors.toMap(s -> s.key(), s -> s.type().convert(s.value())))); } SecurityIdentity userIdentity = augment(user.build(), allAnnotations); diff --git a/test-framework/security/src/main/java/io/quarkus/test/security/SecurityAttribute.java b/test-framework/security/src/main/java/io/quarkus/test/security/SecurityAttribute.java index eba586fa48bae..44dac407f20fa 100644 --- a/test-framework/security/src/main/java/io/quarkus/test/security/SecurityAttribute.java +++ b/test-framework/security/src/main/java/io/quarkus/test/security/SecurityAttribute.java @@ -10,4 +10,6 @@ String key(); String value(); + + AttributeType type() default AttributeType.DEFAULT; }