Skip to content

Commit dd5db17

Browse files
authored
Support for access_token and reduce call to auth store (#517)
2 parents cd84685 + c28ecca commit dd5db17

3 files changed

Lines changed: 66 additions & 45 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ target/
2828

2929
# IDEs
3030
.idea/
31+
*.iml
3132

3233
# When testing JSON files
3334
*.json

src/main/java/land/oras/auth/HttpClient.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -461,10 +461,9 @@ private <T> ResponseWrapper<T> executeRequest(
461461
LOG.debug("New scopes: {}", newScopes.getScopes());
462462

463463
// Add authentication header if any
464-
if (authProvider.getAuthHeader(containerRef) != null
465-
&& !authProvider.getAuthScheme().equals(AuthScheme.NONE)
466-
&& includeAuthHeader) {
467-
builder = builder.header(Const.AUTHORIZATION_HEADER, authProvider.getAuthHeader(containerRef));
464+
var authHeader = authProvider.getAuthHeader(containerRef);
465+
if (authHeader != null && !authProvider.getAuthScheme().equals(AuthScheme.NONE) && includeAuthHeader) {
466+
builder = builder.header(Const.AUTHORIZATION_HEADER, authHeader);
468467
}
469468
headers.forEach(builder::header);
470469

@@ -524,8 +523,16 @@ private <T> ResponseWrapper<T> redoRequest(
524523
token.expires_in(),
525524
token.issued_at().plusSeconds(token.expires_in()));
526525
}
526+
String bearerToken = token.token();
527+
if (bearerToken == null) {
528+
// Docker registry auth spec allows either token or auth_token (or both if they are the same)
529+
bearerToken = token.access_token();
530+
}
531+
if (bearerToken == null) {
532+
throw new OrasException("No Bearer token received");
533+
}
527534
try {
528-
builder = builder.setHeader(Const.AUTHORIZATION_HEADER, "Bearer " + token.token());
535+
builder = builder.setHeader(Const.AUTHORIZATION_HEADER, "Bearer " + bearerToken);
529536
HttpResponse<T> newResponse = client.send(builder.build(), handler);
530537

531538
// Follow redirect

src/test/java/land/oras/RegistryWireMockTest.java

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,11 @@
6060
import org.junit.jupiter.api.io.TempDir;
6161
import org.junit.jupiter.api.parallel.Execution;
6262
import org.junit.jupiter.api.parallel.ExecutionMode;
63-
import org.slf4j.Logger;
64-
import org.slf4j.LoggerFactory;
6563

6664
@WireMockTest
6765
@Execution(ExecutionMode.SAME_THREAD)
6866
class RegistryWireMockTest {
6967

70-
private static final Logger LOG = LoggerFactory.getLogger(RegistryWireMockTest.class);
71-
7268
private final UsernamePasswordProvider authProvider = new UsernamePasswordProvider("myuser", "mypass");
7369

7470
@TempDir
@@ -366,46 +362,23 @@ void shouldRetryBlobUpload(WireMockRuntimeInfo wmRuntimeInfo) throws IOException
366362

367363
@Test
368364
void shouldGetToken(WireMockRuntimeInfo wmRuntimeInfo) {
365+
byte[] blob = tokenScenario(wmRuntimeInfo, "get-token", "token", null);
366+
assertEquals("blob-data", new String(blob));
367+
}
369368

370-
String digest = SupportedAlgorithm.SHA256.digest("blob-data".getBytes());
371-
372-
// Return data from wiremock
373-
WireMock wireMock = wmRuntimeInfo.getWireMock();
374-
wireMock.register(WireMock.any(WireMock.urlEqualTo("/v2/library/get-token/blobs/%s".formatted(digest)))
375-
.inScenario("get token")
376-
.willReturn(WireMock.unauthorized()
377-
.withHeader(
378-
Const.WWW_AUTHENTICATE_HEADER,
379-
"Bearer realm=\"http://localhost:%d/token\",service=\"localhost\",scope=\"repository:library/get-token:pull\""
380-
.formatted(wmRuntimeInfo.getHttpPort()))));
381-
382-
// Return token
383-
wireMock.register(
384-
WireMock.any(WireMock.urlEqualTo("/token?scope=repository:library/get-token:pull&service=localhost"))
385-
.inScenario("get token")
386-
.willSetStateTo("get")
387-
.willReturn(WireMock.okJson(JsonUtils.toJson(new HttpClient.TokenResponse(
388-
"fake-token", "access-token", 300, ZonedDateTime.now())))));
389-
390-
// On the second call we return ok
391-
wireMock.register(WireMock.any(WireMock.urlEqualTo("/v2/library/get-token/blobs/%s".formatted(digest)))
392-
.inScenario("get token")
393-
.whenScenarioStateIs("get")
394-
.willReturn(
395-
WireMock.ok().withBody("blob-data").withHeader(Const.DOCKER_CONTENT_DIGEST_HEADER, digest)));
396-
397-
// Insecure registry
398-
Registry registry = Registry.Builder.builder()
399-
.withAuthProvider(authProvider)
400-
.withInsecure(true)
401-
.build();
402-
403-
ContainerRef containerRef =
404-
ContainerRef.parse("localhost:%d/library/get-token".formatted(wmRuntimeInfo.getHttpPort()));
405-
byte[] blob = registry.getBlob(containerRef.withDigest(digest));
369+
@Test
370+
void shouldGetAuthToken(WireMockRuntimeInfo wmRuntimeInfo) {
371+
byte[] blob = tokenScenario(wmRuntimeInfo, "get-auth-token", null, "access-token");
406372
assertEquals("blob-data", new String(blob));
407373
}
408374

375+
@Test
376+
void shouldThrowIfNoTokenFound(WireMockRuntimeInfo wmRuntimeInfo) {
377+
assertThrows(OrasException.class, () -> {
378+
tokenScenario(wmRuntimeInfo, "get-auth-token", null, null);
379+
});
380+
}
381+
409382
@Test
410383
void shouldRefreshExpiredToken(WireMockRuntimeInfo wmRuntimeInfo) {
411384

@@ -810,4 +783,44 @@ void shouldHandleCorruptedResponse(WireMockRuntimeInfo wmRuntimeInfo) {
810783
"Digest mismatch: sha256:c2752ad96ee652e4d37fd3852de632c50f193490d132f27a1794c986e1f112ef != sha256:2be4e14a6587ab9b637afb553f0654c70e80fa14bd0b8fbf9fa09079f55a2ace",
811784
exception.getMessage());
812785
}
786+
787+
private byte[] tokenScenario(
788+
WireMockRuntimeInfo wmRuntimeInfo, String registryName, String token, String accessToken) {
789+
String digest = SupportedAlgorithm.SHA256.digest("blob-data".getBytes());
790+
791+
// Return data from wiremock
792+
WireMock wireMock = wmRuntimeInfo.getWireMock();
793+
wireMock.register(WireMock.any(WireMock.urlEqualTo("/v2/library/%s/blobs/%s".formatted(registryName, digest)))
794+
.inScenario(registryName)
795+
.willReturn(WireMock.unauthorized()
796+
.withHeader(
797+
Const.WWW_AUTHENTICATE_HEADER,
798+
"Bearer realm=\"http://localhost:%d/token\",service=\"localhost\",scope=\"repository:library/%s:pull\""
799+
.formatted(wmRuntimeInfo.getHttpPort(), registryName))));
800+
801+
// Return token
802+
wireMock.register(WireMock.any(WireMock.urlEqualTo(
803+
"/token?scope=repository:library/%s:pull&service=localhost".formatted(registryName)))
804+
.inScenario(registryName)
805+
.willSetStateTo("get")
806+
.willReturn(WireMock.okJson(
807+
JsonUtils.toJson(new HttpClient.TokenResponse(token, accessToken, 300, ZonedDateTime.now())))));
808+
809+
// On the second call we return ok
810+
wireMock.register(WireMock.any(WireMock.urlEqualTo("/v2/library/%s/blobs/%s".formatted(registryName, digest)))
811+
.inScenario(registryName)
812+
.whenScenarioStateIs("get")
813+
.willReturn(
814+
WireMock.ok().withBody("blob-data").withHeader(Const.DOCKER_CONTENT_DIGEST_HEADER, digest)));
815+
816+
// Insecure registry
817+
Registry registry = Registry.Builder.builder()
818+
.withAuthProvider(authProvider)
819+
.withInsecure(true)
820+
.build();
821+
822+
ContainerRef containerRef =
823+
ContainerRef.parse("localhost:%d/library/%s".formatted(wmRuntimeInfo.getHttpPort(), registryName));
824+
return registry.getBlob(containerRef.withDigest(digest));
825+
}
813826
}

0 commit comments

Comments
 (0)