Skip to content

Commit

Permalink
Merge branch 'main' into quarkus-42563
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsoro authored Sep 21, 2024
2 parents 5672942 + 1453cd3 commit d215502
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,22 @@ public class OidcClientConfig extends OidcClientCommonConfig {
public Optional<List<String>> scopes = Optional.empty();

/**
* Refresh token time skew in seconds.
* If this property is enabled then the configured number of seconds is added to the current time
* Refresh token time skew.
* If this property is enabled then the configured duration is converted to seconds and is added to the current time
* when checking whether the access token should be refreshed. If the sum is greater than this access token's
* expiration time then a refresh is going to happen.
*/
@ConfigItem
public Optional<Duration> refreshTokenTimeSkew = Optional.empty();

/**
* Access token expiration period relative to the current time.
* This property is only checked when an access token grant response
* does not include an access token expiration property.
*/
@ConfigItem
public Optional<Duration> accessTokenExpiresIn = Optional.empty();

/**
* If the access token 'expires_in' property should be checked as an absolute time value
* as opposed to a duration relative to the current time.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@ private Tokens emitGrantTokens(OidcRequestContextProperties requestProps, HttpRe
JsonObject json = buffer.toJsonObject();
// access token
final String accessToken = json.getString(oidcConfig.grant.accessTokenProperty);
final Long accessTokenExpiresAt = getExpiresAtValue(accessToken, json.getValue(oidcConfig.grant.expiresInProperty));
final Long accessTokenExpiresAt = getAccessTokenExpiresAtValue(accessToken,
json.getValue(oidcConfig.grant.expiresInProperty));

final String refreshToken = json.getString(oidcConfig.grant.refreshTokenProperty);
final Long refreshTokenExpiresAt = getExpiresAtValue(refreshToken,
Expand All @@ -261,6 +262,15 @@ private Tokens emitGrantTokens(OidcRequestContextProperties requestProps, HttpRe
}
}

private Long getAccessTokenExpiresAtValue(String token, Object expiresInValue) {
Long expiresAt = getExpiresAtValue(token, expiresInValue);
if (expiresAt == null && oidcConfig.accessTokenExpiresIn.isPresent()) {
final long now = System.currentTimeMillis() / 1000;
expiresAt = now + oidcConfig.accessTokenExpiresIn.get().toSeconds();
}
return expiresAt;
}

private Long getExpiresAtValue(String token, Object expiresInValue) {
if (expiresInValue != null) {
long tokenExpiresIn = expiresInValue instanceof Number ? ((Number) expiresInValue).longValue()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.jboss.resteasy.reactive.server.model.ServerResourceMethod;
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;

import io.quarkus.arc.Arc;
import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveSecurityContext;
import io.quarkus.security.credential.Credential;
import io.quarkus.security.identity.CurrentIdentityAssociation;
Expand Down Expand Up @@ -45,11 +46,11 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti
updateIdentity(requestContext, modified);
}

private void updateIdentity(ResteasyReactiveRequestContext requestContext, SecurityContext modified) {
private static void updateIdentity(ResteasyReactiveRequestContext requestContext, SecurityContext modified) {
requestContext.requireCDIRequestScope();
if (EagerSecurityContext.instance.identityAssociation.isResolvable()) {
final CurrentIdentityAssociation currentIdentityAssociation = getIdentityAssociation();
if (currentIdentityAssociation != null) {
RoutingContext routingContext = requestContext.unwrap(RoutingContext.class);
CurrentIdentityAssociation currentIdentityAssociation = EagerSecurityContext.instance.identityAssociation.get();
Uni<SecurityIdentity> oldIdentity = currentIdentityAssociation.getDeferredIdentity();
currentIdentityAssociation.setIdentity(oldIdentity.map(new Function<SecurityIdentity, SecurityIdentity>() {
@Override
Expand Down Expand Up @@ -119,6 +120,15 @@ public Uni<Boolean> checkPermission(Permission permission) {
}
}

private static CurrentIdentityAssociation getIdentityAssociation() {
if (EagerSecurityContext.instance != null) {
return EagerSecurityContext.instance.identityAssociation.orElse(null);
}
// this should only happen when Quarkus Security extension is not present
// but user implements security themselves, like in their own JAX-RS filter
return Arc.container().instance(CurrentIdentityAssociation.class).orElse(null);
}

public static class Customizer implements HandlerChainCustomizer {
@Override
public List<ServerRestHandler> handlers(Phase phase, ResourceClass resourceClass,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public class FrontendResource {
@NamedOidcClient("non-standard-response")
Tokens tokens;

@Inject
@NamedOidcClient("configured-expires-in")
Tokens tokensConfiguredExpiresIn;

@Inject
@NamedOidcClient("non-standard-response-without-header")
OidcClient tokensWithoutHeader;
Expand Down Expand Up @@ -82,6 +86,16 @@ public String echoTokenNonStandardResponse() {
}
}

@GET
@Path("echoTokenConfiguredExpiresIn")
public String echoTokenConfiguredExpiresIn() {
try {
return tokensConfiguredExpiresIn.getAccessToken() + " " + tokensConfiguredExpiresIn.getAccessTokenExpiresAt();
} catch (OidcClientException ex) {
throw new WebApplicationException(401);
}
}

@GET
@Path("echoTokenNonStandardResponseWithoutHeader")
public Uni<Tokens> echoTokenNonStandardResponseWithoutHeader() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ quarkus.oidc-client.grant.type=password
quarkus.oidc-client.grant-options.password.username=alice
quarkus.oidc-client.grant-options.password.password=alice

quarkus.oidc-client.configured-expires-in.token-path=${keycloak.url}/tokens-without-expires-in
quarkus.oidc-client.configured-expires-in.client-id=quarkus-app
quarkus.oidc-client.configured-expires-in.credentials.client-secret.value=secret
quarkus.oidc-client.configured-expires-in.credentials.client-secret.method=post
quarkus.oidc-client.configured-expires-in.access-token-expires-in=5S

quarkus.oidc-client.jwtbearer.auth-server-url=${keycloak.url}
quarkus.oidc-client.jwtbearer.discovery-enabled=false
quarkus.oidc-client.jwtbearer.token-path=/tokens-jwtbearer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ public Map<String, String> start() {
.withBody(
"{\"access_token\":\"access_token_2\", \"expires_in\":4, \"refresh_token\":\"refresh_token_2\", \"refresh_expires_in\":1}")));

server.stubFor(WireMock.post("/tokens-without-expires-in")
.withRequestBody(matching("grant_type=client_credentials&client_id=quarkus-app&client_secret=secret"))
.willReturn(WireMock
.aResponse()
.withHeader("Content-Type", MediaType.APPLICATION_JSON)
.withBody(
"{\"access_token\":\"access_token_without_expires_in\"}")));

server.stubFor(WireMock.post("/refresh-token-only")
.withRequestBody(
matching("grant_type=refresh_token&refresh_token=shared_refresh_token&extra_param=extra_param_value"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.awaitility.Awaitility.given;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
Expand All @@ -28,6 +29,7 @@
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import io.restassured.response.Response;

@QuarkusTest
@QuarkusTestResource(KeycloakRealmResourceManager.class)
Expand All @@ -44,6 +46,21 @@ public void testEchoTokensJwtBearerAuthentication() {
.body(equalTo("access_token_jwt_bearer"));
}

@Test
public void testGetAccessTokenWithConfiguredExpiresIn() {
Response r = RestAssured.when().get("/frontend/echoTokenConfiguredExpiresIn");
assertEquals(200, r.statusCode());
String[] data = r.body().asString().split(" ");
assertEquals(2, data.length);
assertEquals("access_token_without_expires_in", data[0]);

long now = System.currentTimeMillis() / 1000;
long expectedExpiresAt = now + 5;
long accessTokenExpiresAt = Long.valueOf(data[1]);
assertTrue(accessTokenExpiresAt >= expectedExpiresAt
&& accessTokenExpiresAt <= expectedExpiresAt + 2);
}

@Test
public void testEchoTokensJwtBearerGrant() {
RestAssured.when().get("/frontend/echoTokenJwtBearerGrant")
Expand Down

0 comments on commit d215502

Please sign in to comment.