Skip to content

Commit

Permalink
Merge pull request #29970 from geoand/#10297-security
Browse files Browse the repository at this point in the history
Allow the use of security related annotations to be used as meta-annotations
  • Loading branch information
geoand authored Dec 20, 2022
2 parents 6b36df2 + bd24191 commit aa4ff64
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 34 deletions.
22 changes: 22 additions & 0 deletions docs/src/main/asciidoc/security-jwt.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,28 @@ public class ProtectedResource {
Note that `@TestSecurity` annotation must always be used and its `user` property is returned as `JsonWebToken.getName()` and `roles` property - as `JsonWebToken.getGroups()`.
`@JwtSecurity` annotation is optional and can be used to set the additional token claims.

[TIP]
====
`@TestSecurity` and `@JwtSecurity` can be combined in a meta-annotation, for example like so:
[source, java]
----
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@TestSecurity(user = "userOidc", roles = "viewer")
@OidcSecurity(introspectionRequired = true,
introspection = {
@TokenIntrospection(key = "email", value = "[email protected]")
}
)
public @interface TestSecurityMetaAnnotation {
}
----
This is particularly useful if the same set of security settings needs to be used in multiple test methods.
====

=== How to check the errors in the logs

Please enable `io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator` `TRACE` level logging to see more details about the token verification or decryption errors:
Expand Down
22 changes: 22 additions & 0 deletions docs/src/main/asciidoc/security-openid-connect.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,28 @@ public class ProtectedResource {

Note that `@TestSecurity` `user` and `roles` attributes are available as `TokenIntrospection` `username` and `scope` properties and you can use `io.quarkus.test.security.oidc.TokenIntrospection` to add the additional introspection response properties such as an `email`, etc.

[TIP]
====
`@TestSecurity` and `@OidcSecurity` can be combined in a meta-annotation, for example like so:
[source, java]
----
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@TestSecurity(user = "userOidc", roles = "viewer")
@OidcSecurity(introspectionRequired = true,
introspection = {
@TokenIntrospection(key = "email", value = "[email protected]")
}
)
public @interface TestSecurityMetaAnnotation {
}
----
This is particularly useful if the same set of security settings needs to be used in multiple test methods.
====

=== How to check the errors in the logs

Please enable `io.quarkus.oidc.runtime.OidcProvider` `TRACE` level logging to see more details about the token verification errors:
Expand Down
17 changes: 17 additions & 0 deletions docs/src/main/asciidoc/security-testing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,23 @@ See xref:security-openid-connect.adoc#integration-testing-security-annotation[Op
The feature is only available for `@QuarkusTest` and will **not** work on a `@NativeImageTest` or `@QuarkusIntegrationTest`.
====

[TIP]
====
`@TestSecurity` can also be used in meta-annotations, for example like so:
[source, java]
----
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@TestSecurity(user = "testUser", roles = {"admin", "user"})
public @interface TestSecurityMetaAnnotation {
}
----
This is particularly useful if the same set of security settings needs to be used in multiple test methods.
====

=== Mixing security tests

If it becomes necessary to test security features using both `@TestSecurity` and Basic Auth (which is the fallback auth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

import static org.hamcrest.Matchers.is;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.jupiter.api.Test;

import io.quarkus.test.common.http.TestHTTPEndpoint;
Expand All @@ -25,6 +30,14 @@ public void testWithDummyUser() {
}

@Test
@TestSecurityMetaAnnotation
public void testJwtWithDummyUser() {
RestAssured.when().get("test-security-oidc").then()
.body(is("userOidc:userOidc:userOidc:viewer:[email protected]:subject:aud"));
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@TestSecurity(user = "userOidc", roles = "viewer")
@OidcSecurity(claims = {
@Claim(key = "email", value = "[email protected]")
Expand All @@ -33,9 +46,8 @@ public void testWithDummyUser() {
}, config = {
@ConfigMetadata(key = "audience", value = "aud")
})
public void testJwtWithDummyUser() {
RestAssured.when().get("test-security-oidc").then()
.body(is("userOidc:userOidc:userOidc:viewer:[email protected]:subject:aud"));
public @interface TestSecurityMetaAnnotation {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

import static org.hamcrest.Matchers.is;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.jupiter.api.Test;

import io.quarkus.test.common.http.TestHTTPEndpoint;
Expand All @@ -17,40 +22,61 @@
public class TestSecurityLazyAuthTest {

@Test
@TestSecurity(user = "user1", roles = "viewer")
@TestAsUser1Viewer
public void testWithDummyUser() {
RestAssured.when().get("test-security").then()
.body(is("user1"));
}

@Test
@TestSecurity(user = "user1", roles = "tester")
@TestAsUser1Tester
public void testWithDummyUserForbidden() {
RestAssured.when().get("test-security").then().statusCode(403);
}

@Test
@TestSecurity(user = "user1", roles = "viewer")
@TestAsUser1Viewer
public void testPostWithDummyUser() {
RestAssured.given().contentType(ContentType.JSON).when().body("{\"name\":\"user1\"}").post("test-security").then()
.body(is("user1:user1"));
}

@Test
@TestSecurity(user = "user1", roles = "tester")
@TestAsUser1Tester
public void testPostWithDummyUserForbidden() {
RestAssured.given().contentType(ContentType.JSON).when().body("{\"name\":\"user1\"}").post("test-security").then()
.statusCode(403);
}

@Test
@TestAsUserJwtViewer
public void testJwtGetWithDummyUser() {
RestAssured.when().get("test-security-jwt").then()
.body(is("userJwt:viewer:[email protected]"));
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@TestSecurity(user = "user1", roles = "viewer")
public @interface TestAsUser1Viewer {

}

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@TestSecurity(user = "user1", roles = "tester")
public @interface TestAsUser1Tester {

}

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@TestSecurity(user = "userJwt", roles = "viewer")
@OidcSecurity(claims = {
@Claim(key = "email", value = "[email protected]")
})
public void testJwtGetWithDummyUser() {
RestAssured.when().get("test-security-jwt").then()
.body(is("userJwt:viewer:[email protected]"));
public @interface TestAsUserJwtViewer {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

import static org.hamcrest.Matchers.is;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.jupiter.api.Test;

import io.quarkus.test.common.http.TestHTTPEndpoint;
Expand All @@ -17,40 +22,61 @@
public class TestSecurityLazyAuthTest {

@Test
@TestSecurity(user = "user1", roles = "viewer")
@TestAsUser1Viewer
public void testWithDummyUser() {
RestAssured.when().get("test-security").then()
.body(is("user1:user1:user1"));
}

@Test
@TestSecurity(user = "user1", roles = "tester")
@TestAsUser1Tester
public void testWithDummyUserForbidden() {
RestAssured.when().get("test-security").then().statusCode(403);
}

@Test
@TestSecurity(user = "user1", roles = "viewer")
@TestAsUser1Viewer
public void testPostWithDummyUser() {
RestAssured.given().contentType(ContentType.JSON).when().body("{\"name\":\"user1\"}").post("test-security").then()
.body(is("user1:user1"));
}

@Test
@TestSecurity(user = "user1", roles = "tester")
@TestAsUser1Tester
public void testPostWithDummyUserForbidden() {
RestAssured.given().contentType(ContentType.JSON).when().body("{\"name\":\"user1\"}").post("test-security").then()
.statusCode(403);
}

@Test
@TestAsUserJwtViewer
public void testJwtGetWithDummyUser() {
RestAssured.when().get("test-security-jwt").then()
.body(is("userJwt:userJwt:userJwt:viewer:[email protected]"));
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@TestSecurity(user = "user1", roles = "viewer")
public @interface TestAsUser1Viewer {

}

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@TestSecurity(user = "user1", roles = "tester")
public @interface TestAsUser1Tester {

}

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@TestSecurity(user = "userJwt", roles = "viewer")
@JwtSecurity(claims = {
@Claim(key = "email", value = "[email protected]")
})
public void testJwtGetWithDummyUser() {
RestAssured.when().get("test-security-jwt").then()
.body(is("userJwt:userJwt:userJwt:viewer:[email protected]"));
public @interface TestAsUserJwtViewer {

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.quarkus.test.util.annotations;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;

public final class AnnotationContainer<A extends Annotation> {

private final AnnotatedElement element;
private final A annotation;

public AnnotationContainer(AnnotatedElement element, A annotation) {
this.element = element;
this.annotation = annotation;
}

public AnnotatedElement getElement() {
return element;
}

public A getAnnotation() {
return annotation;
}
}
Loading

0 comments on commit aa4ff64

Please sign in to comment.