diff --git a/src/main/java/land/oras/Registry.java b/src/main/java/land/oras/Registry.java index fa73187a..d4757c04 100644 --- a/src/main/java/land/oras/Registry.java +++ b/src/main/java/land/oras/Registry.java @@ -42,6 +42,7 @@ import java.util.function.Supplier; import land.oras.auth.AuthProvider; import land.oras.auth.AuthStoreAuthenticationProvider; +import land.oras.auth.BearerTokenProvider; import land.oras.auth.HttpClient; import land.oras.auth.NoAuthProvider; import land.oras.auth.RegistriesConf; @@ -294,6 +295,15 @@ public Registry copy(String newRegistry) { return new Builder().from(this).withRegistry(newRegistry).build(); } + /** + * Return a new registry with a new auth external token and same settings + * @param authToken The new refreshed token + * @return The new registry + */ + public Registry withAuthToken(String authToken) { + return new Builder().from(this).withAuthToken(authToken).build(); + } + /** * Return a new registry as insecure but with same settings * @return The new registry @@ -1281,6 +1291,18 @@ public Builder withAuthProvider(AuthProvider authProvider) { return this; } + /** + * Use given auth token for the registry. + * Useful when the auth token is obtained by other mean (like a token exchange). + * Caller are responsible to handle token expiration if any + * @param authToken The auth token + * @return The builder + */ + public Builder withAuthToken(String authToken) { + registry.setAuthProvider(new BearerTokenProvider(authToken)); + return this; + } + /** * Set the maximum number of concurrent downloads when pulling an artifact with multiple layers. Default is 4. * @param parallelism The maximum number of parallel uploads/download diff --git a/src/main/java/land/oras/auth/BearerTokenProvider.java b/src/main/java/land/oras/auth/BearerTokenProvider.java index 29a93f4e..87ea0c1c 100644 --- a/src/main/java/land/oras/auth/BearerTokenProvider.java +++ b/src/main/java/land/oras/auth/BearerTokenProvider.java @@ -47,6 +47,14 @@ public final class BearerTokenProvider implements AuthProvider { */ public BearerTokenProvider() {} + /** + * Create a new bearer token provider + * @param token The token + */ + public BearerTokenProvider(String token) { + setToken(new HttpClient.TokenResponse(token, null, null, null, null)); + } + /** * Get the token * @return The token diff --git a/src/test/java/land/oras/RegistryWireMockTest.java b/src/test/java/land/oras/RegistryWireMockTest.java index 4b1bdf78..80322e00 100644 --- a/src/test/java/land/oras/RegistryWireMockTest.java +++ b/src/test/java/land/oras/RegistryWireMockTest.java @@ -83,6 +83,34 @@ class RegistryWireMockTest { @TempDir private static Path homeDir3; + @Test + void shouldPassBearerTokenWithExternalRequestedToken(WireMockRuntimeInfo wmRuntimeInfo) { + Registry registry = Registry.Builder.builder() + .insecure() + .withAuthToken("insecure-token") + .build(); + + // Ensure WireMock accept only our token + WireMock wireMock = wmRuntimeInfo.getWireMock(); + wireMock.register(WireMock.get(WireMock.urlEqualTo("/v2/library/artifact-text/tags/list")) + .withHeader("Authorization", equalTo("Bearer insecure-token")) + .willReturn(WireMock.okJson(JsonUtils.toJson(new Tags("artifact-text", List.of("latest", "0.1.1")))))); + wireMock.register(WireMock.get(WireMock.urlEqualTo("/v2/library/artifact-text/tags/list")) + .withHeader("Authorization", equalTo("Bearer invalid-token")) + .willReturn(WireMock.unauthorized())); + + registry.getTags(ContainerRef.parse("%s/library/artifact-text" + .formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")))); + + // Ensure it fail with invalid token + final Registry newRegistry = registry.withAuthToken("invalid-token"); + OrasException e = assertThrows( + OrasException.class, + () -> newRegistry.getTags(ContainerRef.parse("%s/library/artifact-text" + .formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", ""))))); + assertEquals(401, e.getStatusCode()); + } + @Test void shouldFailToGetManifestOn403(WireMockRuntimeInfo wmRuntimeInfo) {