Skip to content

Commit

Permalink
Make sure the code flow access token is propagated during the authent…
Browse files Browse the repository at this point in the history
…ication

(cherry picked from commit 1346815)
  • Loading branch information
sberyozkin authored and gsmet committed Feb 6, 2024
1 parent 7b2e9fe commit 1f5e4bc
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 13 deletions.
5 changes: 5 additions & 0 deletions extensions/oidc-token-propagation-reactive/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.eclipse.microprofile.rest.client.inject.RestClient;

import io.quarkus.security.identity.CurrentIdentityAssociation;

@Path("/frontend")
public class FrontendResource {
@Inject
Expand All @@ -19,9 +17,6 @@ public class FrontendResource {
@Inject
JsonWebToken jwt;

@Inject
CurrentIdentityAssociation identityAssociation;

@GET
@Path("token-propagation")
@RolesAllowed("admin")
Expand All @@ -31,7 +26,7 @@ public String userNameTokenPropagation() {

@GET
@Path("token-propagation-with-augmentor")
@RolesAllowed("tester") // tester role is granted by SecurityIdentityAugmentor
@RolesAllowed("Bearertester") // Bearertester role is granted by SecurityIdentityAugmentor
public String userNameTokenPropagationWithSecIdentityAugmentor() {
return getResponseWithExchangedUsername();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@

import static io.quarkus.oidc.token.propagation.reactive.RolesSecurityIdentityAugmentor.SUPPORTED_USER;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.io.IOException;
import java.util.Set;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import com.gargoylesoftware.htmlunit.SilentCssErrorHandler;
import com.gargoylesoftware.htmlunit.TextPage;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.oidc.server.OidcWiremockTestResource;
Expand Down Expand Up @@ -51,4 +59,25 @@ public String getBearerAccessToken() {
return OidcWiremockTestResource.getAccessToken(SUPPORTED_USER, Set.of("admin"));
}

@Test
public void testGetUserNameWithTokenPropagationWithCodeFlow() throws IOException, InterruptedException {
try (final WebClient webClient = createWebClient()) {
HtmlPage page = webClient.getPage("http://localhost:8081/frontend/token-propagation-with-augmentor");

HtmlForm form = page.getFormByName("form");
form.getInputByName("username").type("alice");
form.getInputByName("password").type("alice");

TextPage textPage = form.getInputByValue("login").click();

assertEquals("Token issued to alice has been exchanged, new user name: bob", textPage.getContent());
webClient.getCookieManager().clearCookies();
}
}

private WebClient createWebClient() {
WebClient webClient = new WebClient();
webClient.setCssErrorHandler(new SilentCssErrorHandler());
return webClient;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public class RolesResource {
@GET
public String get() {
if ("bob".equals(jwt.getName())) {
return "tester";
String tokenType = jwt.getClaim("typ");
return tokenType + "tester";
}
throw new ForbiddenException("Only user 'bob' is allowed to request roles");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=hybrid
quarkus.oidc.token.audience=any

quarkus.oidc-client.auth-server-url=${quarkus.oidc.auth-server-url}
quarkus.oidc-client.client-id=${quarkus.oidc.client-id}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.quarkus.oidc.runtime;

import io.quarkus.oidc.AccessTokenCredential;
import io.quarkus.oidc.IdTokenCredential;
import io.quarkus.oidc.common.runtime.OidcConstants;
import io.quarkus.security.credential.TokenCredential;
import io.quarkus.security.identity.IdentityProviderManager;
import io.quarkus.security.identity.SecurityIdentity;
Expand Down Expand Up @@ -42,7 +45,12 @@ protected Uni<SecurityIdentity> authenticate(IdentityProviderManager identityPro
// during authentication TokenCredential is not accessible via CDI, thus we put it to the duplicated context
VertxContextSafetyToggle.validateContextIfExists(ERROR_MSG, ERROR_MSG);
final var ctx = Vertx.currentContext();
ctx.putLocal(TokenCredential.class.getName(), token);
// If the primary token is ID token then the code flow access token is available as
// a RoutingContext `access_token` property.
final var tokenCredential = (token instanceof IdTokenCredential)
? new AccessTokenCredential(context.get(OidcConstants.ACCESS_TOKEN_VALUE))
: token;
ctx.putLocal(TokenCredential.class.getName(), tokenCredential);
return identityProviderManager
.authenticate(HttpSecurityUtils.setRoutingContextAttribute(new TokenAuthenticationRequest(token), context))
.invoke(new Runnable() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import io.smallrye.jwt.build.Jwt;
import io.smallrye.jwt.build.JwtClaimsBuilder;

/**
* Provides a mock OIDC server to tests.
Expand All @@ -42,6 +43,10 @@ public class OidcWiremockTestResource implements QuarkusTestResourceLifecycleMan
"https://server.example.com");
private static final String TOKEN_AUDIENCE = System.getProperty("quarkus.test.oidc.token.audience",
"https://server.example.com");
private static final String TOKEN_SUBJECT = "123456";
private static final String BEARER_TOKEN_TYPE = "Bearer";
private static final String ID_TOKEN_TYPE = "ID";

private static final String TOKEN_USER_ROLES = System.getProperty("quarkus.test.oidc.token.user-roles", "user");
private static final String TOKEN_ADMIN_ROLES = System.getProperty("quarkus.test.oidc.token.admin-roles", "user,admin");
private static final String ENCODED_X5C = "MIIC+zCCAeOgAwIBAgIGAXx/E9rgMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMTEwMTQxMzUzMDBaFw0yMjEwMTQxMzUzMDBaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIicN95dXlQLBqEZUsqPhQopnjnPgGmW80NohEgNzZLqN0xW9cyJJrdJM5Z1lRrePHZGiJdd1XXn4fYasP6/cjRfMWal9X6dD5wlnOTP01/4beX5vctE6W4lZrI3kTFmZ+I69w7BaLsUPWgV1CYrtuldL3dr6xAnngK3hU+JraB2Ndw9llXib26HOZhCXKedCTYcUQieVJGPI0f8H1JNk88+PnwI+cUGgXHF56iTLv9QujI6AhIgextXdd21T0XiHgBkSlSSBeqIKAjfCW6zoXP+PJU+Lso24J3duG3mrbilqHZlmIWnLRaG0RmKOeedXIDHvAaMaVUOLaN9HBgNKo0CAwEAAaNTMFEwHQYDVR0OBBYEFMYGoBNHBTMvMT4DwClVHVVwn+5VMB8GA1UdIwQYMBaAFMYGoBNHBTMvMT4DwClVHVVwn+5VMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFulB0DKhykXGbGPIBPcj63ItLNilgl1i8i43my8fYdV6OBWLIhZ4InhpX1+XmYCNPNtu94Jy1csS00K2/Hhn4ByBd+6nd5DSr0W0VdVQyhLz3GW1nf0J3X2N+tD818O0KtKKPTq4p9reg/XtV+DNv7DeDAGzlfgRL4E4fQx6OYeuu35kGrPvAddIA70leJMELJRylCLfEcl2ne/Bht8cZVp7ZCxnfXnsc+7hCW84mhzGjJycA3E6TnZPD3pD+q9FoIAQMxMQqUCH71u9vTvz1Q5JdokuJJY2eTHSUKyHA9MwSFq8DFDICJFBoQuFyDlK5yxSUcQpR3mBwKdimj6oA0=";
Expand Down Expand Up @@ -364,24 +369,33 @@ private Set<String> getUserRoles() {
}

public static String getAccessToken(String userName, Set<String> groups) {
return generateJwtToken(userName, groups);
return generateJwtToken(userName, groups, TOKEN_SUBJECT, BEARER_TOKEN_TYPE);
}

public static String getIdToken(String userName, Set<String> groups) {
return generateJwtToken(userName, groups);
return generateJwtToken(userName, groups, TOKEN_SUBJECT, ID_TOKEN_TYPE);
}

public static String generateJwtToken(String userName, Set<String> groups) {
return generateJwtToken(userName, groups, "123456");
return generateJwtToken(userName, groups, TOKEN_SUBJECT);
}

public static String generateJwtToken(String userName, Set<String> groups, String sub) {
return Jwt.preferredUserName(userName)
return generateJwtToken(userName, groups, sub, null);
}

public static String generateJwtToken(String userName, Set<String> groups, String sub, String type) {
JwtClaimsBuilder builder = Jwt.preferredUserName(userName)
.groups(groups)
.issuer(TOKEN_ISSUER)
.audience(TOKEN_AUDIENCE)
.claim("sid", "session-id")
.subject(sub)
.subject(sub);
if (type != null) {
builder.claim("typ", type);
}

return builder
.jws()
.keyId("1")
.sign("privateKey.jwk");
Expand Down

0 comments on commit 1f5e4bc

Please sign in to comment.