-
Notifications
You must be signed in to change notification settings - Fork 39
feat(http): add oauth2 client_credentials support #813
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
Changes from 1 commit
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
fc09947
add oauth2 client_credentials support
ozozgun 1a418dc
use BASIC_AUTH for getting bearer token from oauth2 server
ozozgun 948c7e4
update authenticated steps gherkin phrases
ozozgun 1b49fcf
fix tests
ozozgun bb186ca
add tests for erroneous cases
ozozgun c3f956b
refactor tests, update doc
ozozgun 53f7677
prevent unnecessary call to oauth2 server if client already registered
ozozgun 0ca9435
refactor authentication step's gherkin statement
ozozgun a15bc0e
refactor authentication step's gherkin statement
ozozgun 07bec28
try git guardian config file
ozozgun 1a8caf5
try git guardian config file
ozozgun 81541ae
remove gitguardian config file
ozozgun cb8605b
fix authentication step base method
ozozgun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
tzatziki-http/src/main/java/com/decathlon/tzatziki/utils/OAuth2ClientCredentialsStore.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| package com.decathlon.tzatziki.utils; | ||
|
|
||
| import lombok.AccessLevel; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
|
|
||
| /** | ||
| * Store for OAuth2 client credentials configurations and cached access tokens. | ||
| * <p> | ||
| * This class manages OAuth2 client credentials (clientId, clientSecret, tokenUrl) and | ||
| * caches the fetched access tokens per clientId. Tokens are fetched once when the client | ||
| * is registered and cached for the duration of the test scenario. | ||
| * </p> | ||
| */ | ||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
| public final class OAuth2ClientCredentialsStore { | ||
|
|
||
| private static final Map<String, OAuth2ClientConfig> clientConfigs = new ConcurrentHashMap<>(); | ||
| private static final Map<String, String> accessTokens = new ConcurrentHashMap<>(); | ||
|
|
||
| /** | ||
| * Registers a new OAuth2 client and immediately fetches the access token. | ||
| * | ||
| * @param clientId the OAuth2 client ID | ||
| * @param clientSecret the OAuth2 client secret | ||
| * @param tokenUrl the OAuth2 token endpoint URL | ||
| * @throws AssertionError if token fetch fails | ||
| */ | ||
| public static void registerClient(String clientId, String clientSecret, String tokenUrl) { | ||
| OAuth2ClientConfig config = new OAuth2ClientConfig(clientId, clientSecret, tokenUrl); | ||
| clientConfigs.put(clientId, config); | ||
|
|
||
| // Fetch token immediately and cache it | ||
| String accessToken = OAuth2TokenFetcher.fetchAccessToken(clientId, clientSecret, tokenUrl); | ||
| accessTokens.put(clientId, accessToken); | ||
| } | ||
|
|
||
| /** | ||
| * Gets the cached access token for the given clientId. | ||
| * | ||
| * @param clientId the OAuth2 client ID | ||
| * @return the cached access token | ||
| * @throws AssertionError if no token is found for the clientId | ||
| */ | ||
| public static String getAccessToken(String clientId) { | ||
| String token = accessTokens.get(clientId); | ||
| if (token == null) { | ||
| throw new AssertionError("No OAuth2 access token found for clientId: " + clientId + | ||
| ". Please setup authentication first using: Setup authentication for clientId \"" + | ||
| clientId + "\" with clientSecret \"...\" and token url \"...\""); | ||
| } | ||
| return token; | ||
| } | ||
|
|
||
| /** | ||
| * Checks if a client is registered. | ||
| * | ||
| * @param clientId the OAuth2 client ID | ||
| * @return true if the client is registered, false otherwise | ||
| */ | ||
| public static boolean hasClient(String clientId) { | ||
| return clientConfigs.containsKey(clientId); | ||
| } | ||
|
|
||
| /** | ||
| * Resets the store, clearing all cached tokens and configurations. | ||
| * Should be called between test scenarios. | ||
| */ | ||
| public static void reset() { | ||
| clientConfigs.clear(); | ||
| accessTokens.clear(); | ||
| } | ||
|
|
||
| /** | ||
| * Internal configuration holder for OAuth2 client credentials. | ||
| */ | ||
| public record OAuth2ClientConfig(String clientId, String clientSecret, String tokenUrl) { | ||
| } | ||
| } |
74 changes: 74 additions & 0 deletions
74
tzatziki-http/src/main/java/com/decathlon/tzatziki/utils/OAuth2TokenFetcher.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package com.decathlon.tzatziki.utils; | ||
|
|
||
| import io.restassured.response.Response; | ||
| import lombok.AccessLevel; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
| import static io.restassured.RestAssured.given; | ||
|
|
||
| /** | ||
| * Utility class for fetching OAuth2 access tokens using the client credentials flow. | ||
| * <p> | ||
| * This class performs HTTP POST requests to OAuth2 token endpoints to obtain | ||
| * access tokens. It throws immediately on any failure. | ||
| * </p> | ||
| */ | ||
| @Slf4j | ||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
| public final class OAuth2TokenFetcher { | ||
|
|
||
| private static final String GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"; | ||
|
|
||
| /** | ||
| * Fetches an access token from the OAuth2 token endpoint using client credentials flow. | ||
| * | ||
| * @param clientId the OAuth2 client ID | ||
| * @param clientSecret the OAuth2 client secret | ||
| * @param tokenUrl the OAuth2 token endpoint URL | ||
| * @return the access token | ||
| * @throws AssertionError if the token request fails or the response is invalid | ||
| */ | ||
| public static String fetchAccessToken(String clientId, String clientSecret, String tokenUrl) { | ||
| log.debug("Fetching OAuth2 access token for clientId: {} from: {}", clientId, tokenUrl); | ||
|
|
||
| // Resolve the token URL through HttpWiremockUtils to support mocked endpoints | ||
| String resolvedTokenUrl = HttpWiremockUtils.target(tokenUrl); | ||
| log.debug("Resolved token URL: {}", resolvedTokenUrl); | ||
|
|
||
| try { | ||
| Response response = given() | ||
| .contentType("application/x-www-form-urlencoded") | ||
| .formParam("grant_type", GRANT_TYPE_CLIENT_CREDENTIALS) | ||
| .formParam("client_id", clientId) | ||
| .formParam("client_secret", clientSecret) | ||
ozozgun marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| .post(resolvedTokenUrl); | ||
|
|
||
| int statusCode = response.getStatusCode(); | ||
| if (statusCode < 200 || statusCode >= 300) { | ||
| throw new AssertionError( | ||
| "OAuth2 token request failed for clientId: " + clientId + | ||
| ". Status: " + statusCode + | ||
| ". Response: " + response.getBody().asString()); | ||
| } | ||
|
|
||
| String accessToken = response.jsonPath().getString("access_token"); | ||
| if (accessToken == null || accessToken.isBlank()) { | ||
| throw new AssertionError( | ||
| "OAuth2 token response does not contain 'access_token' for clientId: " + clientId + | ||
| ". Response: " + response.getBody().asString()); | ||
| } | ||
|
|
||
| log.debug("Successfully fetched OAuth2 access token for clientId: {}", clientId); | ||
| return accessToken; | ||
|
|
||
| } catch (Error e) { | ||
| if (e instanceof AssertionError) { | ||
| throw e; | ||
| } | ||
| throw new AssertionError( | ||
| "Failed to fetch OAuth2 access token for clientId: " + clientId + | ||
| " from: " + tokenUrl + ". Error: " + e.getMessage(), e); | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.