Skip to content

Commit

Permalink
Apply the required claims restriction to OIDC introspections
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin committed Oct 23, 2024
1 parent 8d60463 commit 3d7db50
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,27 @@ public TokenIntrospection apply(TokenIntrospection introspectionResult, Throwabl
throw new AuthenticationFailedException(ex);
}

if (requiredClaims != null && !requiredClaims.isEmpty()) {
for (Map.Entry<String, String> requiredClaim : requiredClaims.entrySet()) {
String introspectionClaimValue = null;
try {
introspectionClaimValue = introspectionResult.getString(requiredClaim.getKey());
} catch (ClassCastException ex) {
LOG.debugf("Introspection claim %s is not String", requiredClaim.getKey());
throw new AuthenticationFailedException();
}
if (introspectionClaimValue == null) {
LOG.debugf("Introspection claim %s is missing", requiredClaim.getKey());
throw new AuthenticationFailedException();
}
if (!introspectionClaimValue.equals(requiredClaim.getValue())) {
LOG.debugf("Value of the introspection claim %s does not match required value of %s",
requiredClaim.getKey(), requiredClaim.getValue());
throw new AuthenticationFailedException();
}
}
}

return introspectionResult;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.it.keycloak;

import java.time.Duration;
import java.util.Map;
import java.util.function.Supplier;

import jakarta.enterprise.context.ApplicationScoped;
Expand Down Expand Up @@ -52,6 +53,8 @@ public OidcTenantConfig get() {
// authServerUri points to the JAX-RS `OidcResource`, root path is `/oidc`
final String authServerUri;
if (path.contains("tenant-opaque")) {
config.token.setRequiredClaims(Map.of("required_claim", "1"));

if (path.endsWith("/tenant-opaque/tenant-oidc/api/user")) {
authServerUri = uri.replace("/tenant-opaque/tenant-oidc/api/user", "/oidc");
} else if (path.endsWith("/tenant-opaque/tenant-oidc/api/user-permission")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class OidcResource {
private volatile boolean rotate;
private volatile int jwkEndpointCallCount;
private volatile int introspectionEndpointCallCount;
private volatile int opaqueToken2UsageCount;
private volatile int revokeEndpointCallCount;
private volatile int userInfoEndpointCallCount;
private volatile boolean enableDiscovery = true;
Expand Down Expand Up @@ -112,6 +113,13 @@ public int resetIntrospectionEndpointCallCount() {
return introspectionEndpointCallCount;
}

@POST
@Path("opaque-token-call-count")
public int resetOpaqueTokenCallCount() {
opaqueToken2UsageCount = 0;
return opaqueToken2UsageCount;
}

@POST
@Produces("application/json")
@Path("introspect")
Expand All @@ -120,7 +128,15 @@ public String introspect(@FormParam("client_id") String clientId, @FormParam("cl
introspectionEndpointCallCount++;

boolean activeStatus = introspection && !token.endsWith("-invalid");

boolean requiredClaim = true;
if (token.endsWith("_2")) {
opaqueToken2UsageCount++;
if (opaqueToken2UsageCount == 2) {
// This is to confirm that the same opaque token_2 works well when its introspection response
// includes `required_claim` with value "1" but fails when the required claim is not included
requiredClaim = false;
}
}
String introspectionClientId = "none";
String introspectionClientSecret = "none";
if (clientSecret != null) {
Expand All @@ -146,6 +162,7 @@ public String introspect(@FormParam("client_id") String clientId, @FormParam("cl
" \"scope\": \"user\"," +
" \"email\": \"[email protected]\"," +
" \"username\": \"alice\"," +
(requiredClaim ? "\"required_claim\": \"1\"," : "") +
" \"introspection_client_id\": \"" + introspectionClientId + "\"," +
" \"introspection_client_secret\": \"" + introspectionClientSecret + "\"," +
" \"client_id\": \"" + clientId + "\"" +
Expand Down Expand Up @@ -251,13 +268,23 @@ public String testAccessTokenWithEmptyScope(@QueryParam("kid") String kid, @Quer
@POST
@Path("opaque-token")
@Produces("application/json")
public String testOpaqueToken(@QueryParam("kid") String kid) {
public String testOpaqueToken() {
return "{\"access_token\": \"987654321\"," +
" \"token_type\": \"Bearer\"," +
" \"refresh_token\": \"123456789\"," +
" \"expires_in\": 300 }";
}

@POST
@Path("opaque-token2")
@Produces("application/json")
public String testOpaqueToken2() {
return "{\"access_token\": \"987654321_2\"," +
" \"token_type\": \"Bearer\"," +
" \"refresh_token\": \"123456789\"," +
" \"expires_in\": 300 }";
}

@POST
@Path("enable-introspection")
public boolean setIntrospection() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,22 @@ public void testOpaqueTokenScopePermission() {
.when().get("/tenant-opaque/tenant-oidc/api/admin-permission")
.then()
.statusCode(403);

// Successful request with opaque token 2
String opaqueToken2 = getOpaqueAccessToken2FromSimpleOidc();
RestAssured.given().auth().oauth2(opaqueToken2)
.when().get("/tenant-opaque/tenant-oidc/api/user-permission")
.then()
.statusCode(200)
.body(equalTo("user"));

// Expected to fail now because its introspection does not include the expected required claim
RestAssured.given().auth().oauth2(opaqueToken2)
.when().get("/tenant-opaque/tenant-oidc/api/user-permission")
.then()
.statusCode(401);

RestAssured.when().post("/oidc/opaque-token-call-count").then().body(equalTo("0"));
}

@Test
Expand Down Expand Up @@ -900,6 +916,15 @@ private String getOpaqueAccessTokenFromSimpleOidc() {
return object.getString("access_token");
}

private String getOpaqueAccessToken2FromSimpleOidc() {
String json = RestAssured
.when()
.post("/oidc/opaque-token2")
.body().asString();
JsonObject object = new JsonObject(json);
return object.getString("access_token");
}

static WebClient createWebClient() {
WebClient webClient = new WebClient();
webClient.setCssErrorHandler(new SilentCssErrorHandler());
Expand Down

0 comments on commit 3d7db50

Please sign in to comment.