Skip to content

Commit

Permalink
Add a test
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin authored and Eng-Fouad committed Jul 7, 2024
1 parent d68580c commit 7736fe5
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import static org.jboss.jandex.AnnotationTarget.Kind.METHOD;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BooleanSupplier;

import jakarta.inject.Singleton;
Expand All @@ -27,12 +29,14 @@
import io.quarkus.arc.deployment.BeanDiscoveryFinishedBuildItem;
import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem;
import io.quarkus.arc.deployment.InjectionPointTransformerBuildItem;
import io.quarkus.arc.deployment.QualifierRegistrarBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem;
import io.quarkus.arc.processor.Annotations;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.arc.processor.InjectionPointInfo;
import io.quarkus.arc.processor.InjectionPointsTransformer;
import io.quarkus.arc.processor.QualifierRegistrar;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.Feature;
Expand All @@ -51,6 +55,7 @@
import io.quarkus.oidc.BearerTokenAuthentication;
import io.quarkus.oidc.IdToken;
import io.quarkus.oidc.Tenant;
import io.quarkus.oidc.TenantFeature;
import io.quarkus.oidc.TenantIdentityProvider;
import io.quarkus.oidc.TokenIntrospectionCache;
import io.quarkus.oidc.UserInfo;
Expand Down Expand Up @@ -84,6 +89,7 @@
@BuildSteps(onlyIf = OidcBuildStep.IsEnabled.class)
public class OidcBuildStep {
private static final DotName TENANT_NAME = DotName.createSimple(Tenant.class);
private static final DotName TENANT_FEATURE_NAME = DotName.createSimple(TenantFeature.class);
private static final DotName TENANT_IDENTITY_PROVIDER_NAME = DotName.createSimple(TenantIdentityProvider.class);
private static final Logger LOG = Logger.getLogger(OidcBuildStep.class);
private static final DotName USER_INFO_NAME = DotName.createSimple(UserInfo.class);
Expand Down Expand Up @@ -151,6 +157,19 @@ ExtensionSslNativeSupportBuildItem enableSslInNative() {
return new ExtensionSslNativeSupportBuildItem(Feature.OIDC);
}

@BuildStep
QualifierRegistrarBuildItem addQualifiers() {
// this seems to be necessary; I think it's because sometimes we only access beans
// annotated with @TenantFeature programmatically and no injection point is annotated with it
// TODO: drop @TenantFeature qualifier when 'TenantFeatureFinder' stop using this annotation as a qualifier
return new QualifierRegistrarBuildItem(new QualifierRegistrar() {
@Override
public Map<DotName, Set<String>> getAdditionalQualifiers() {
return Map.of(TENANT_FEATURE_NAME, Set.of());
}
});
}

@BuildStep
InjectionPointTransformerBuildItem makeTenantIdentityProviderInjectionPointsNamed() {
// @Tenant annotation cannot be a qualifier as it is used on resource methods and lead to illegal states
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static TokenCustomizer find(OidcTenantConfig oidcConfig) {
}
} else if (oidcConfig.tenantId.isPresent()) {
String tenantId = oidcConfig.tenantId.get();
List<TokenCustomizer> list = findTenantFeaturesByTenantId(TokenCustomizer.class, tenantId);
List<TokenCustomizer> list = findTenantFeaturesByTenantId(TokenCustomizer.class, tenantId, container);
if (!list.isEmpty()) {
if (list.size() >= 2) {
throw new OIDCException(
Expand All @@ -54,32 +54,33 @@ public static TokenCustomizer find(OidcTenantConfig oidcConfig) {

public static <T> List<T> find(OidcTenantConfig oidcTenantConfig, Class<T> tenantFeatureClass) {
if (oidcTenantConfig != null && oidcTenantConfig.tenantId.isPresent()) {
var tenantsValidators = new ArrayList<T>();
for (var instance : Arc.container().listAll(tenantFeatureClass, Default.Literal.INSTANCE)) {
if (instance.isAvailable()) {
tenantsValidators.add(instance.get());
ArcContainer container = Arc.container();
if (container != null) {
var tenantsValidators = new ArrayList<T>();
for (var instance : container.listAll(tenantFeatureClass, Default.Literal.INSTANCE)) {
if (instance.isAvailable()) {
tenantsValidators.add(instance.get());
}
}
tenantsValidators
.addAll(findTenantFeaturesByTenantId(tenantFeatureClass, oidcTenantConfig.tenantId.get(), container));
if (!tenantsValidators.isEmpty()) {
return List.copyOf(tenantsValidators);
}
}
tenantsValidators.addAll(findTenantFeaturesByTenantId(tenantFeatureClass, oidcTenantConfig.tenantId.get()));
if (!tenantsValidators.isEmpty()) {
return List.copyOf(tenantsValidators);
}
}
return List.of();
}

private static <T> List<T> findTenantFeaturesByTenantId(Class<T> tenantFeatureClass, String tenantId) {
ArcContainer container = Arc.container();
if (container != null) {
List<T> list = new ArrayList<>();
for (T tenantFeature : container.listAll(tenantFeatureClass).stream().map(InstanceHandle::get).toList()) {
TenantFeature annotation = ClientProxy.unwrap(tenantFeature).getClass().getAnnotation(TenantFeature.class);
if (annotation != null && Arrays.asList(annotation.value()).contains(tenantId)) {
list.add(tenantFeature);
}
private static <T> List<T> findTenantFeaturesByTenantId(Class<T> tenantFeatureClass, String tenantId,
ArcContainer container) {
List<T> list = new ArrayList<>();
for (T tenantFeature : container.listAll(tenantFeatureClass).stream().map(InstanceHandle::get).toList()) {
TenantFeature annotation = ClientProxy.unwrap(tenantFeature).getClass().getAnnotation(TenantFeature.class);
if (annotation != null && Arrays.asList(annotation.value()).contains(tenantId)) {
list.add(tenantFeature);
}
return list;
}
return List.of();
return list;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ public String admin() {
return "granted:" + identity.getRoles();
}

@Path("custombearer")
@GET
@RolesAllowed("admin")
@Produces(MediaType.APPLICATION_JSON)
public String customBearerAdmin() {
return "granted:" + identity.getRoles();
}

@Path("bearer-required-algorithm")
@GET
@RolesAllowed("admin")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.quarkus.it.keycloak;

import jakarta.inject.Singleton;
import jakarta.json.Json;
import jakarta.json.JsonObject;

import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.TenantFeature;
import io.quarkus.oidc.TokenCustomizer;

@Singleton
@TenantFeature({ "bearer", "custombearer" })
@Unremovable
public class BearerTenantsTokenCustomizer implements TokenCustomizer {

volatile int rs256CountBearer;
volatile int rs256CountCustomBearer;

@Override
public JsonObject customizeHeaders(JsonObject headers) {
String customizeHeader = null;
if (headers.containsKey("customize_bearer")) {
customizeHeader = "customize_bearer";
} else if (headers.containsKey("customize_custombearer")) {
customizeHeader = "customize_custombearer";
}
if (customizeHeader == null) {
return null;
}
String alg = headers.getString("alg");
if ("RS256".equals(alg)) {
if ("customize_bearer".equals(customizeHeader)) {
if (0 == rs256CountBearer++) {
return null;
} else {
return Json.createObjectBuilder(headers).remove(customizeHeader).build();
}
} else {
if (0 == rs256CountCustomBearer++) {
return null;
} else {
return Json.createObjectBuilder(headers).remove(customizeHeader).build();
}
}
} else if ("RS384".equals(alg)) {
return null;
} else if ("RS512".equals(alg)) {
return Json.createObjectBuilder(headers).add("alg", "RS256").build();
}
return null;
}

}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ quarkus.oidc.bearer.credentials.secret=secret
quarkus.oidc.bearer.token.audience=https://service.example.com
quarkus.oidc.bearer.allow-token-introspection-cache=false

quarkus.oidc.custombearer.auth-server-url=${keycloak.url}/realms/quarkus/
quarkus.oidc.custombearer.client-id=quarkus-app
quarkus.oidc.custombearer.credentials.secret=secret
quarkus.oidc.custombearer.token.audience=https://service.example.com
quarkus.oidc.custombearer.allow-token-introspection-cache=false

quarkus.oidc.bearer-kid-or-chain.auth-server-url=${keycloak.url}/realms/quarkus/
quarkus.oidc.bearer-kid-or-chain.client-id=quarkus-app
quarkus.oidc.bearer-kid-or-chain.credentials.secret=secret
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,37 +94,42 @@ private String readFile(String filePath) throws Exception {

@Test
public void testAccessAdminResource() {
doTestAccessAdminResource("bearer");
doTestAccessAdminResource("custombearer");
}

private static void doTestAccessAdminResource(String tenant) {
String token = Jwt.preferredUserName("admin")
.groups(Set.of("admin"))
.issuer("https://server.example.com")
.audience("https://service.example.com")
.jws().header("customize", true)
.jws().header("customize_" + tenant, true)
.sign();

// 1st pass with `RS256` - OK
RestAssured.given().auth().oauth2(token)
.when().get("/api/admin/bearer")
.when().get("/api/admin/" + tenant)
.then()
.statusCode(200)
.body(Matchers.containsString("admin"));

// 2nd pass with `RS256` - 401 because the customizer removes `customize` header
RestAssured.given().auth().oauth2(token)
.when().get("/api/admin/bearer")
.when().get("/api/admin/" + tenant)
.then()
.statusCode(401);

// replacing RS256 with RS384 fails - the customizer does nothing
token = setTokenAlgorithm(token, "RS384");
RestAssured.given().auth().oauth2(token)
.when().get("/api/admin/bearer")
.when().get("/api/admin/" + tenant)
.then()
.statusCode(401);

// replacing RS256 with RS512 - OK, customizer sets the algorithm to the original RS256
token = setTokenAlgorithm(token, "RS512");
RestAssured.given().auth().oauth2(token)
.when().get("/api/admin/bearer")
.when().get("/api/admin/" + tenant)
.then()
.statusCode(200)
.body(Matchers.containsString("admin"));
Expand Down

0 comments on commit 7736fe5

Please sign in to comment.