diff --git a/CHANGELOG.md b/CHANGELOG.md index 532935d..589984c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,30 @@ ## Release (2025-MM-DD) -- `loadbalancer`: [v0.1.0](services/loadbalancer/CHANGELOG.md#v010) - - Initial onboarding of STACKIT Java SDK for Load balancer service -- `alb`: [v0.1.0](services/alb/CHANGELOG.md#v010) - - Initial onboarding of STACKIT Java SDK for Application load balancer service -- `objectstorage`: [v0.1.0](services/objectstorage/CHANGELOG.md#v010) - - Initial onboarding of STACKIT Java SDK for Object storage service -- `serverupdate`: [v0.1.0](services/serverupdate/CHANGELOG.md#v010) - - Initial onboarding of STACKIT Java SDK for Server Update service +- `core`: [v0.4.1](core/CHANGELOG.md/#v041) + - **Bugfix:** Add check in `KeyFlowAuthenticator` to prevent endless loops +- `iaas`: [v0.3.1](services/iaas/CHANGELOG.md#v031) + - Bump dependency `cloud.stackit.sdk.core` to v0.4.1 +- `resourcemanager`: [v0.4.1](services/resourcemanager/CHANGELOG.md#v041) + - Bump dependency `cloud.stackit.sdk.core` to v0.4.1 +- `loadbalancer`: + - [v0.1.1](services/loadbalancer/CHANGELOG.md#v011) + - Bump dependency `cloud.stackit.sdk.core` to v0.4.1 + - [v0.1.0](services/loadbalancer/CHANGELOG.md#v010) + - Initial onboarding of STACKIT Java SDK for Load balancer service +- `alb`: + - [v0.1.1](services/alb/CHANGELOG.md#v011) + - Bump dependency `cloud.stackit.sdk.core` to v0.4.1 + - [v0.1.0](services/alb/CHANGELOG.md#v010) + - Initial onboarding of STACKIT Java SDK for Application load balancer service +- `objectstorage`: + - [v0.1.1](services/objectstorage/CHANGELOG.md#v011) + - Bump dependency `cloud.stackit.sdk.core` to v0.4.1 + - [v0.1.0](services/objectstorage/CHANGELOG.md#v010) + - Initial onboarding of STACKIT Java SDK for Object storage service +- `serverupdate`: + - [v0.1.1](services/serverupdate/CHANGELOG.md#v011) + - Bump dependency `cloud.stackit.sdk.core` to v0.4.1 + - [v0.1.0](services/serverupdate/CHANGELOG.md#v010) + - Initial onboarding of STACKIT Java SDK for Server Update service ## Release (2025-10-29) - `core`: diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index ee493e4..63f379e 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -1,3 +1,6 @@ +## v0.4.1 +- **Bugfix:** Add check in `KeyFlowAuthenticator` to prevent endless loops + ## v0.4.0 - **Feature:** Added core wait handler structure which can be used by every service waiter implementation. diff --git a/core/VERSION b/core/VERSION index 1d0ba9e..267577d 100644 --- a/core/VERSION +++ b/core/VERSION @@ -1 +1 @@ -0.4.0 +0.4.1 diff --git a/core/src/main/java/cloud/stackit/sdk/core/KeyFlowAuthenticator.java b/core/src/main/java/cloud/stackit/sdk/core/KeyFlowAuthenticator.java index 2cb552b..9d7bb64 100644 --- a/core/src/main/java/cloud/stackit/sdk/core/KeyFlowAuthenticator.java +++ b/core/src/main/java/cloud/stackit/sdk/core/KeyFlowAuthenticator.java @@ -52,6 +52,12 @@ public class KeyFlowAuthenticator implements Authenticator { /** * Creates the initial service account and refreshes expired access token. * + *

NOTE: It's normal that 2 requests are sent, it's regular OkHttp Authenticator behavior. + * The first request is always attempted without the authenticator and in case the response is + * Unauthorized(=401), OkHttp reattempt the request with the authenticator. See OkHttp + * Docs + * * @deprecated use constructor with OkHttpClient instead to prevent resource leaks. Will be * removed in April 2026. * @param cfg Configuration to set a custom token endpoint and the token expiration leeway. @@ -65,6 +71,12 @@ public KeyFlowAuthenticator(CoreConfiguration cfg, ServiceAccountKey saKey) { /** * Creates the initial service account and refreshes expired access token. * + *

NOTE: It's normal that 2 requests are sent, it's regular OkHttp Authenticator behavior. + * The first request is always attempted without the authenticator and in case the response is + * Unauthorized(=401), OkHttp reattempt the request with the authenticator. See OkHttp + * Docs + * * @deprecated use constructor with OkHttpClient instead to prevent resource leaks. Will be * removed in April 2026. * @param cfg Configuration to set a custom token endpoint and the token expiration leeway. @@ -81,6 +93,12 @@ public KeyFlowAuthenticator( /** * Creates the initial service account and refreshes expired access token. * + *

NOTE: It's normal that 2 requests are sent, it's regular OkHttp Authenticator behavior. + * The first request is always attempted without the authenticator and in case the response is + * Unauthorized(=401), OkHttp reattempt the request with the authenticator. See OkHttp + * Docs + * * @param httpClient OkHttpClient object * @param cfg Configuration to set a custom token endpoint and the token expiration leeway. */ @@ -91,6 +109,12 @@ public KeyFlowAuthenticator(OkHttpClient httpClient, CoreConfiguration cfg) thro /** * Creates the initial service account and refreshes expired access token. * + *

NOTE: It's normal that 2 requests are sent, it's regular OkHttp Authenticator behavior. + * The first request is always attempted without the authenticator and in case the response is + * Unauthorized(=401), OkHttp reattempt the request with the authenticator. See OkHttp + * Docs + * * @param httpClient OkHttpClient object * @param cfg Configuration to set a custom token endpoint and the token expiration leeway. * @param saKey Service Account Key, which should be used for the authentication @@ -129,6 +153,9 @@ protected KeyFlowAuthenticator( @Override public Request authenticate(Route route, @NotNull Response response) throws IOException { + if (response.request().header("Authorization") != null) { + return null; // Give up, we've already attempted to authenticate. + } String accessToken; try { accessToken = getAccessToken(); diff --git a/core/src/test/java/cloud/stackit/sdk/core/KeyFlowAuthenticatorTest.java b/core/src/test/java/cloud/stackit/sdk/core/KeyFlowAuthenticatorTest.java index 7f763ce..1e8fc6b 100644 --- a/core/src/test/java/cloud/stackit/sdk/core/KeyFlowAuthenticatorTest.java +++ b/core/src/test/java/cloud/stackit/sdk/core/KeyFlowAuthenticatorTest.java @@ -16,8 +16,7 @@ import java.security.spec.InvalidKeySpecException; import java.time.temporal.ChronoUnit; import java.util.Date; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; +import okhttp3.*; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.AfterEach; @@ -62,6 +61,9 @@ class KeyFlowAuthenticatorTest { + "h/9afEtu5aUE/m+1vGBoH8z1\n" + "-----END PRIVATE KEY-----\n"; + private static final Request mockRequest = + new Request.Builder().url("https://stackit.com").get().build(); + private ServiceAccountKey createDummyServiceAccount() { ServiceAccountCredentials credentials = new ServiceAccountCredentials("aud", "iss", "kid", PRIVATE_KEY, "sub"); @@ -270,4 +272,92 @@ void createAccessTokenWithRefreshTokenResponse200WithEmptyBodyThrowsException() assertThrows( JsonSyntaxException.class, keyFlowAuthenticator::createAccessTokenWithRefreshToken); } + + @Test + @DisplayName("authenticator sets Authorization header") + void authenticatorSetsAuthorizationHeaderIfNotAuthenticated() + throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { + // Setup mockServer + final String authorizationHeader = "Authorization"; + KeyFlowAuthenticator.KeyFlowTokenResponse mockResponse = mockResponseBody(false); + // mock response for KeyFlow authentication with mocked access token + MockResponse mockedResponse = + new MockResponse() + .setResponseCode(200) + .setBody(new Gson().toJson(mockResponse)) + .addHeader("Content-type", "application/json"); + mockWebServer.enqueue(mockedResponse); + HttpUrl url = mockWebServer.url(MOCK_WEBSERVER_PATH); + + // Set unauthorized request + Response unauthorizedRequest = + new Response.Builder() + .request(mockRequest) + .code(401) + .message("Unauthorized") + .protocol(Protocol.HTTP_2) + .build(); + + // Config + CoreConfiguration cfg = + new CoreConfiguration().tokenCustomUrl(url.toString()); // Use mockWebServer + + // Check if "Authorization" header is unset + assertNull(unauthorizedRequest.request().header(authorizationHeader)); + + // Prepare keyFlowAuthenticator + KeyFlowAuthenticator keyFlowAuthenticator = + new KeyFlowAuthenticator(httpClient, cfg, createDummyServiceAccount()); + // authenticator creates new access token and sets it the Authorization header + Request newRequest = keyFlowAuthenticator.authenticate(null, unauthorizedRequest); + + // Check if new request is not null + assertNotNull(newRequest); + // Check if the "Authorization" Header is set + assertNotNull(newRequest.header(authorizationHeader)); + } + + @Test + @DisplayName("Authenticator returns null when already authenticated") + void authenticatorReturnsNullWhenAlreadyAuthenticated() + throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { + // Setup mockServer + final String authorizationHeader = "Authorization"; + KeyFlowAuthenticator.KeyFlowTokenResponse mockResponse = mockResponseBody(false); + // mock response for KeyFlow authentication with mocked access token + MockResponse mockedResponse = + new MockResponse() + .setResponseCode(200) + .setBody(new Gson().toJson(mockResponse)) + .addHeader("Content-type", "application/json"); + mockWebServer.enqueue(mockedResponse); + HttpUrl url = mockWebServer.url(MOCK_WEBSERVER_PATH); + + // Set unauthorized request + Response unauthorizedRequest = + new Response.Builder() + .request( + mockRequest + .newBuilder() + .addHeader(authorizationHeader, "") + .build()) + .code(401) + .message("Unauthorized") + .protocol(Protocol.HTTP_2) + .build(); // Unauthorized request + + // Config + CoreConfiguration cfg = + new CoreConfiguration().tokenCustomUrl(url.toString()); // Use mockWebServer + + // Check if "Authorization" header is set + assertNotNull(unauthorizedRequest.request().header(authorizationHeader)); + + // Prepare keyFlowAuthenticator + KeyFlowAuthenticator keyFlowAuthenticator = + new KeyFlowAuthenticator(httpClient, cfg, createDummyServiceAccount()); + + // Authenticator returns no new request, because "Authorization" header was already set + assertNull(keyFlowAuthenticator.authenticate(null, unauthorizedRequest)); + } } diff --git a/services/alb/CHANGELOG.md b/services/alb/CHANGELOG.md index 72726d0..85e6668 100644 --- a/services/alb/CHANGELOG.md +++ b/services/alb/CHANGELOG.md @@ -1,2 +1,5 @@ +## v0.1.1 +- Bump dependency `cloud.stackit.sdk.core` to v0.4.1 + ## v0.1.0 - Initial onboarding of STACKIT Java SDK for Application load balancer service \ No newline at end of file diff --git a/services/alb/VERSION b/services/alb/VERSION index 6c6aa7c..6da28dd 100644 --- a/services/alb/VERSION +++ b/services/alb/VERSION @@ -1 +1 @@ -0.1.0 \ No newline at end of file +0.1.1 \ No newline at end of file diff --git a/services/iaas/CHANGELOG.md b/services/iaas/CHANGELOG.md index 053e5f5..0ddc8c7 100644 --- a/services/iaas/CHANGELOG.md +++ b/services/iaas/CHANGELOG.md @@ -1,3 +1,6 @@ +## v0.3.1 +- Bump dependency `cloud.stackit.sdk.core` to v0.4.1 + ## v0.3.0 - **Feature:** Add `createdAt` and `updatedAt` attributes to `SecurityGroupRule`, `BaseSecurityGroupRule`, `CreateSecurityGroupRulePayload` model classes - **Feature:** Add `description` attribute to `CreateNicPayload`, `NIC`, `UpdateNicPayload` model classes diff --git a/services/iaas/VERSION b/services/iaas/VERSION index 0d91a54..9e11b32 100644 --- a/services/iaas/VERSION +++ b/services/iaas/VERSION @@ -1 +1 @@ -0.3.0 +0.3.1 diff --git a/services/loadbalancer/CHANGELOG.md b/services/loadbalancer/CHANGELOG.md index 183bddc..483829f 100644 --- a/services/loadbalancer/CHANGELOG.md +++ b/services/loadbalancer/CHANGELOG.md @@ -1,2 +1,5 @@ +## v0.1.1 +- Bump dependency `cloud.stackit.sdk.core` to v0.4.1 + ## v0.1.0 - Initial onboarding of STACKIT Java SDK for Load balancer service diff --git a/services/loadbalancer/VERSION b/services/loadbalancer/VERSION index 6e8bf73..17e51c3 100644 --- a/services/loadbalancer/VERSION +++ b/services/loadbalancer/VERSION @@ -1 +1 @@ -0.1.0 +0.1.1 diff --git a/services/objectstorage/CHANGELOG.md b/services/objectstorage/CHANGELOG.md index 2e083ab..4f6e4ae 100644 --- a/services/objectstorage/CHANGELOG.md +++ b/services/objectstorage/CHANGELOG.md @@ -1,2 +1,5 @@ +## v0.1.1 +- Bump dependency `cloud.stackit.sdk.core` to v0.4.1 + ## v0.1.0 - Initial onboarding of STACKIT Java SDK for Object storage service diff --git a/services/objectstorage/VERSION b/services/objectstorage/VERSION index 6e8bf73..17e51c3 100644 --- a/services/objectstorage/VERSION +++ b/services/objectstorage/VERSION @@ -1 +1 @@ -0.1.0 +0.1.1 diff --git a/services/resourcemanager/CHANGELOG.md b/services/resourcemanager/CHANGELOG.md index b453eb3..093ae7c 100644 --- a/services/resourcemanager/CHANGELOG.md +++ b/services/resourcemanager/CHANGELOG.md @@ -1,3 +1,6 @@ +## v0.4.1 +- Bump dependency `cloud.stackit.sdk.core` to v0.4.1 + ## v0.4.0 - **Feature:** Added waiter for project creation and project deletion diff --git a/services/resourcemanager/VERSION b/services/resourcemanager/VERSION index 1d0ba9e..267577d 100644 --- a/services/resourcemanager/VERSION +++ b/services/resourcemanager/VERSION @@ -1 +1 @@ -0.4.0 +0.4.1 diff --git a/services/serverupdate/CHANGELOG.md b/services/serverupdate/CHANGELOG.md index 89281e1..92c3fe2 100644 --- a/services/serverupdate/CHANGELOG.md +++ b/services/serverupdate/CHANGELOG.md @@ -1,2 +1,5 @@ +## v0.1.1 +- Bump dependency `cloud.stackit.sdk.core` to v0.4.1 + ## v0.1.0 - Initial onboarding of STACKIT Java SDK for Server Update service \ No newline at end of file diff --git a/services/serverupdate/VERSION b/services/serverupdate/VERSION index 6c6aa7c..6da28dd 100644 --- a/services/serverupdate/VERSION +++ b/services/serverupdate/VERSION @@ -1 +1 @@ -0.1.0 \ No newline at end of file +0.1.1 \ No newline at end of file