Skip to content

Commit 920f83c

Browse files
authored
Merge pull request #50700 from michalvavrik/feature/test-security-on-test-class
Fix scenario that combines class-level `@TestSecurity` annotation with method-level `@OidcSecurity` annotation
2 parents 3a4e42e + 6b0d1e5 commit 920f83c

File tree

2 files changed

+71
-8
lines changed

2 files changed

+71
-8
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.quarkus.it.keycloak;
2+
3+
import static org.hamcrest.Matchers.is;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import io.quarkus.test.common.http.TestHTTPEndpoint;
8+
import io.quarkus.test.junit.QuarkusTest;
9+
import io.quarkus.test.security.TestSecurity;
10+
import io.quarkus.test.security.oidc.Claim;
11+
import io.quarkus.test.security.oidc.OidcSecurity;
12+
import io.restassured.RestAssured;
13+
14+
@TestSecurity(user = "user1", roles = "viewer")
15+
@QuarkusTest
16+
@TestHTTPEndpoint(ProtectedJwtResource.class)
17+
public class ClassLevelTestSecurityLazyAuthTest {
18+
19+
@Test
20+
public void testClassAnnotationWithDummyUser() {
21+
RestAssured.when().get("test-security").then().body(is("user1"));
22+
}
23+
24+
@Test
25+
@OidcSecurity(claims = {
26+
@Claim(key = "email", value = "[email protected]")
27+
})
28+
public void testClassAnnotationWithOidcSecurity() {
29+
// verify information form security context
30+
RestAssured.when().get("test-security").then().body(is("user1"));
31+
// verify information from JWT
32+
RestAssured.when().get("test-security-jwt").then().body(is("user1:viewer:[email protected]"));
33+
}
34+
35+
@Test
36+
@TestSecurity(user = "userJwt", roles = "viewer")
37+
@OidcSecurity(claims = {
38+
@Claim(key = "email", value = "[email protected]")
39+
})
40+
public void testMethodLevelTestSecurityOverridesClassAnnotation() {
41+
RestAssured.when().get("test-security-jwt").then().body(is("userJwt:viewer:[email protected]"));
42+
}
43+
44+
}

test-framework/security/src/main/java/io/quarkus/test/security/QuarkusSecurityTestExtension.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.Set;
1616
import java.util.function.Function;
1717
import java.util.stream.Collectors;
18+
import java.util.stream.Stream;
1819

1920
import jakarta.enterprise.inject.Instance;
2021
import jakarta.enterprise.inject.spi.CDI;
@@ -39,7 +40,7 @@ public class QuarkusSecurityTestExtension implements QuarkusTestBeforeEachCallba
3940
@Override
4041
public void afterEach(QuarkusTestMethodContext context) {
4142
try {
42-
if (getAnnotationContainer(context).isPresent()) {
43+
if (getTestSecurityContext(context).isPresent()) {
4344
final ArcContainer container = Arc.container();
4445
container.select(TestAuthController.class).get().setEnabled(true);
4546
for (var testMechanism : container.select(AbstractTestHttpAuthenticationMechanism.class)) {
@@ -60,13 +61,12 @@ public void afterEach(QuarkusTestMethodContext context) {
6061
@Override
6162
public void beforeEach(QuarkusTestMethodContext context) {
6263
try {
63-
Optional<AnnotationContainer<TestSecurity>> annotationContainerOptional = getAnnotationContainer(context);
64-
if (annotationContainerOptional.isEmpty()) {
64+
var testSecurityContext = getTestSecurityContext(context);
65+
if (!testSecurityContext.isPresent()) {
6566
return;
6667
}
67-
var annotationContainer = annotationContainerOptional.get();
68-
Annotation[] allAnnotations = annotationContainer.getElement().getAnnotations();
69-
TestSecurity testSecurity = annotationContainer.getAnnotation();
68+
Annotation[] allAnnotations = testSecurityContext.allAnnotations();
69+
TestSecurity testSecurity = testSecurityContext.annotationContainer.getAnnotation();
7070
final ArcContainer container = Arc.container();
7171
container.select(TestAuthController.class).get().setEnabled(testSecurity.authorizationEnabled());
7272
if (testSecurity.user().isEmpty()) {
@@ -163,7 +163,7 @@ void addAction(String action) {
163163
possessedPermissions.stream().anyMatch(possessedPermission -> possessedPermission.implies(requiredPermission)));
164164
}
165165

166-
private Optional<AnnotationContainer<TestSecurity>> getAnnotationContainer(QuarkusTestMethodContext context)
166+
private TestSecurityContext getTestSecurityContext(QuarkusTestMethodContext context)
167167
throws Exception {
168168
//the usual ClassLoader hacks to get our copy of the TestSecurity annotation
169169
ClassLoader cl = Thread.currentThread().getContextClassLoader();
@@ -183,8 +183,9 @@ private Optional<AnnotationContainer<TestSecurity>> getAnnotationContainer(Quark
183183
TestSecurity.class);
184184
if (annotationContainerOptional.isEmpty()) {
185185
annotationContainerOptional = AnnotationUtils.findAnnotation(original, TestSecurity.class);
186+
return new TestSecurityContext(annotationContainerOptional.orElse(null), method);
186187
}
187-
return annotationContainerOptional;
188+
return new TestSecurityContext(annotationContainerOptional.orElse(null), null);
188189
}
189190

190191
private SecurityIdentity augment(SecurityIdentity identity, Annotation[] annotations) {
@@ -194,4 +195,22 @@ private SecurityIdentity augment(SecurityIdentity identity, Annotation[] annotat
194195
}
195196
return identity;
196197
}
198+
199+
private record TestSecurityContext(AnnotationContainer<TestSecurity> annotationContainer, Method method) {
200+
private Annotation[] allAnnotations() {
201+
Annotation[] testSecurityElementAnnotations = annotationContainer.getElement().getAnnotations();
202+
boolean classLevelTestSecurity = method != null;
203+
if (classLevelTestSecurity && method.getAnnotations().length > 0) {
204+
// add method-level annotations as there could be for example @OidcSecurity
205+
return Stream.concat(
206+
Arrays.stream(method.getAnnotations()),
207+
Arrays.stream(testSecurityElementAnnotations)).toArray(Annotation[]::new);
208+
}
209+
return testSecurityElementAnnotations;
210+
}
211+
212+
private boolean isPresent() {
213+
return annotationContainer != null;
214+
}
215+
}
197216
}

0 commit comments

Comments
 (0)