Skip to content

Extract expiry time from access token and regenerate if it expires #294

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Solid Specification Conformance Test Harness

## Release 1.1.12
### Minor changes
* Fix JSON logging output for result counts.
* Detect expired access tokens and regenerate when needed.

## Release 1.1.11
### Minor changes
* Improve logging of initial config and provide better information for errors.
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
<artifactId>quarkus-junit5-mockito</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-jwt-build</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand All @@ -240,6 +244,7 @@
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>${wiremock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hashids</groupId>
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/solid/testharness/config/TestSubject.java
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,9 @@ URI provisionPod() {
public void tearDownServer() {
try {
if (testRunContainer != null) {
logger.info("TEAR DOWN {}", testRunContainer.getUrl());
testRunContainer.delete();
logger.info("TEAR DOWN COMPLETE");
}
} catch (Exception e) {
// log failure but continue to report results
Expand Down
77 changes: 31 additions & 46 deletions src/main/java/org/solid/testharness/http/AuthManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jose4j.jwk.JsonWebKeySet;
import org.jose4j.lang.JoseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.solid.testharness.config.Config;
Expand Down Expand Up @@ -124,38 +126,38 @@ public SolidClientProvider authenticate(@NotNull final String user) {
clientRegistry.register(user, authClient);

final OidcConfiguration oidcConfiguration = requestOidcConfiguration(authClient, oidcIssuer);
final JsonWebKeySet jsonWebKeySet = requestJwks(authClient, oidcConfiguration);
authClient.setJsonWebKeySet(jsonWebKeySet);

final Tokens tokens;
if (userConfig.isUsingUsernamePassword()) {
// create client with session support for login
final Client sessionClient = new Client.Builder()
.withSessionSupport()
.withOptionalLocalhostSupport(oidcIssuer)
.build();
tokens = loginAndGetAccessToken(authClient, userConfig, oidcConfiguration, sessionClient);
loginAndGetAccessToken(authClient, userConfig, oidcConfiguration, sessionClient);
} else if (userConfig.isUsingRefreshToken()) {
tokens = exchangeRefreshToken(authClient, userConfig, oidcConfiguration);
exchangeRefreshToken(authClient, userConfig, oidcConfiguration);
} else if (userConfig.isUsingClientCredentials()) {
tokens = clientCredentialsAccessToken(authClient, userConfig, oidcConfiguration);
clientCredentialsAccessToken(authClient, userConfig, oidcConfiguration);
} else {
logger.warn("Insufficient UserCredentials for {}: {}", user, userConfig.stringValue());
throw new TestHarnessInitializationException(MessageFormat.format(
"Neither login credentials nor refresh token details provided for {0}: [{1}]",
user, userConfig.webId()));
}
authClient.setAccessToken(tokens.getAccessToken());
}
return new SolidClientProvider(authClient);
}

Tokens exchangeRefreshToken(final Client authClient, final UserCredentials userConfig,
void exchangeRefreshToken(final Client authClient, final UserCredentials userConfig,
final OidcConfiguration oidcConfig) {
logger.info("Exchange refresh token for {}: [{}]", authClient.getUser(), userConfig.webId());
if (!oidcConfig.getGrantTypesSupported().contains(HttpConstants.REFRESH_TOKEN)) {
throw new TestHarnessInitializationException(IDP_GRANT_ERROR +
HttpConstants.REFRESH_TOKEN);
}
return requestToken(authClient, oidcConfig,
requestToken(authClient, oidcConfig,
userConfig.clientId().orElseThrow(),
userConfig.clientSecret().orElseThrow(),
Map.of(
Expand All @@ -165,15 +167,15 @@ Tokens exchangeRefreshToken(final Client authClient, final UserCredentials userC
);
}

Tokens clientCredentialsAccessToken(final Client authClient, final UserCredentials userConfig,
void clientCredentialsAccessToken(final Client authClient, final UserCredentials userConfig,
final OidcConfiguration oidcConfig) {
logger.info("Use client credentials to get access token for {}: [{}]",
authClient.getUser(), userConfig.webId());
if (!oidcConfig.getGrantTypesSupported().contains(HttpConstants.CLIENT_CREDENTIALS)) {
throw new TestHarnessInitializationException(IDP_GRANT_ERROR +
HttpConstants.CLIENT_CREDENTIALS);
}
return requestToken(authClient, oidcConfig,
requestToken(authClient, oidcConfig,
userConfig.clientId().orElseThrow(),
userConfig.clientSecret().orElseThrow(),
Map.of(
Expand All @@ -182,7 +184,7 @@ Tokens clientCredentialsAccessToken(final Client authClient, final UserCredentia
);
}

Tokens loginAndGetAccessToken(final Client authClient, final UserCredentials userConfig,
void loginAndGetAccessToken(final Client authClient, final UserCredentials userConfig,
final OidcConfiguration oidcConfig, final Client sessionClient) {
logger.info("Login and get access token for {}: [{}]", authClient.getUser(), userConfig.webId());
if (!oidcConfig.getGrantTypesSupported().contains(HttpConstants.AUTHORIZATION_CODE_TYPE)) {
Expand All @@ -203,7 +205,7 @@ Tokens loginAndGetAccessToken(final Client authClient, final UserCredentials use
final String authCode = requestAuthorizationCode(sessionClient, oidcConfig, appOrigin, clientId,
userConfig, codeVerifier);

return requestToken(authClient, oidcConfig,
requestToken(authClient, oidcConfig,
clientId,
clientRegistration.getClientSecret(),
Map.of(
Expand Down Expand Up @@ -235,6 +237,19 @@ OidcConfiguration requestOidcConfiguration(final Client client, final URI oidcIs
}
}

JsonWebKeySet requestJwks(final Client client, final OidcConfiguration oidcConfig) {
logger.debug("\n========== GET JSON WEB KEY SET");
final HttpResponse<String> response = getRequest(client, oidcConfig.getJwksEndpoint(),
HttpConstants.MEDIA_TYPE_APPLICATION_JSON, "JSON Web Key Set");
try {
final String json = response.body();
return new JsonWebKeySet(json);
} catch (JoseException e) {
throw new TestHarnessInitializationException("Failed to read the JSON Web Key Set at " +
oidcConfig.getJwksEndpoint(), e);
}
}

void startLoginSession(final Client client, final UserCredentials userConfig, final URI loginEndpoint) {
logger.debug("\n========== START SESSION");
postRequest(client, loginEndpoint,
Expand Down Expand Up @@ -340,38 +355,12 @@ URI idpLogin(final Client client, final URI loginEndpoint, final UserCredentials
}
}

Tokens requestToken(final Client authClient, final OidcConfiguration oidcConfig,
final String clientId, final String clientSecret,
final Map<Object, Object> tokenRequestData) {
void requestToken(final Client authClient, final OidcConfiguration oidcConfig,
final String clientId, final String clientSecret,
final Map<Object, Object> tokenRequestData) {
logger.debug("\n========== ACCESS TOKEN");
final String authHeader = HttpConstants.PREFIX_BASIC + base64Encode(clientId + ':' + clientSecret);
final HttpRequest.Builder requestBuilder = authClient.signRequest(
HttpUtils.newRequestBuilder(oidcConfig.getTokenEndpoint())
.header(HttpConstants.HEADER_AUTHORIZATION, authHeader)
.header(HttpConstants.HEADER_CONTENT_TYPE, HttpConstants.MEDIA_TYPE_APPLICATION_FORM_URLENCODED)
.header(HttpConstants.HEADER_ACCEPT, HttpConstants.MEDIA_TYPE_APPLICATION_JSON)
.POST(HttpUtils.ofFormData(tokenRequestData))
);
final HttpResponse<String> response;
try {
final HttpRequest request = requestBuilder.build();
HttpUtils.logRequest(logger, request);
response = authClient.send(request, HttpResponse.BodyHandlers.ofString());
HttpUtils.logResponse(logger, response);
} catch (Exception e) {
throw new TestHarnessInitializationException("Token exchange request failed", e);
}
final String body = response.body();
if (response.statusCode() != HttpConstants.STATUS_OK) {
logger.error("FAILED TO GET ACCESS TOKEN {}", body);
throw new TestHarnessInitializationException("Token exchange failed for grant type: " +
tokenRequestData.get(HttpConstants.GRANT_TYPE));
}
try {
return objectMapper.readValue(response.body(), Tokens.class);
} catch (Exception e) {
throw new TestHarnessInitializationException("Failed to parse token response", e);
}
authClient.saveTokenRequestData(oidcConfig, clientId, clientSecret, tokenRequestData);
authClient.requestAccessToken();
}

HttpResponse<String> getRequest(final Client client, final URI uri, final String type, final String stage) {
Expand Down Expand Up @@ -416,10 +405,6 @@ <T> HttpResponse<T> postRequest(final Client client, final URI uri,
return response;
}

private String base64Encode(final String data) {
return Base64.getEncoder().encodeToString(data.getBytes());
}

private String generateCodeVerifier() {
final SecureRandom secureRandom = new SecureRandom();
final byte[] codeVerifier = new byte[32];
Expand Down
Loading