8000 Pass each time auth provider and handle token refresh on client instead of registry by jonesbusy · Pull Request #264 · oras-project/oras-java · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Pass each time auth provider and handle token refresh on client instead of registry #264

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
Apr 18, 2025
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
193 changes: 60 additions & 133 deletions src/main/java/land/oras/Registry.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,9 @@ public String getPassword() {
public String getAuthHeader(ContainerRef registry) {
return "Basic " + java.util.Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
}

@Override
public AuthScheme getAuthScheme() {
return AuthScheme.BASIC;
}
}
6 changes: 6 additions & 0 deletions src/main/java/land/oras/auth/AuthProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,10 @@ public interface AuthProvider {
*/
@Nullable
String getAuthHeader(ContainerRef registry);

/**
* Get the authentication scheme for this provider
* @return The authentication scheme
*/
AuthScheme getAuthScheme();
}
42 changes: 42 additions & 0 deletions src/main/java/land/oras/auth/AuthScheme.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*-
* =LICENSE=
* ORAS Java SDK
* ===
* Copyright (C) 2024 - 2025 ORAS
* ===
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =LICENSEEND=
*/

package land.oras.auth;

/**
* Enum for authentication schemes
*/
public enum AuthScheme {

/**
* No authentication
*/
NONE,

/**
* Basic authentication
*/
BASIC,

/**
* Bearer authentication
*/
BEARER,
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,9 @@ public String getAuthHeader(ContainerRef registry) {
}
return new UsernamePasswordProvider(credential.username(), credential.password()).getAuthHeader(registry);
}

@Override
public AuthScheme getAuthScheme() {
return AuthScheme.BASIC;
}
}
113 changes: 16 additions & 97 deletions src/main/java/land/oras/auth/BearerTokenProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,8 @@

package land.oras.auth;

import java.net.URI;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import land.oras.ContainerRef;
import land.oras.exception.OrasException;
import land.oras.utils.Const;
import land.oras.utils.JsonUtils;
import land.oras.utils.OrasHttpClient;
import land.oras.utils.HttpClient;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
Expand All @@ -43,12 +33,6 @@
@NullMarked
public final class BearerTokenProvider implements AuthProvider {

/**
* The pattern for the WWW-Authenticate header value
*/
private static final Pattern WWW_AUTH_VALUE_PATTERN =
Pattern.compile("Bearer realm=\"([^\"]+)\",service=\"([^\"]+)\",scope=\"([^\"]+)\"(,error=\"([^\"]+)\")?");

/**
* Logger
*/
Expand All @@ -57,105 +41,40 @@
/**
* The refreshed token
*/
private @Nullable TokenResponse token;

/**
* The provider for username and password in case of refresh token done
*/
private final AuthProvider provider;
private HttpClient.@Nullable TokenResponse token;

/**
* Create a new bearer token provider
* @param provider The provider for username and password
*/
public BearerTokenProvider(AuthProvider provider) {
this.provider = provider;
}
public BearerTokenProvider() {}

/**
* Retrieve
* @param response The response
* @param client The original client
* @param containerRef The container reference
* Get the token
* @return The token
*/
public BearerTokenProvider refreshToken(
ContainerRef containerRef, OrasHttpClient client, OrasHttpClient.ResponseWrapper<String> response) {

String wwwAuthHeader = response.headers().getOrDefault(Const.WWW_AUTHENTICATE_HEADER.toLowerCase(), "");
LOG.debug("WWW-Authenticate header: {}", wwwAuthHeader);
if (wwwAuthHeader.isEmpty()) {
throw new OrasException("No WWW-Authenticate header found in response");
}

Matcher matcher = WWW_AUTH_VALUE_PATTERN.matcher(wwwAuthHeader);
if (!matcher.matches()) {
throw new OrasException("Invalid WWW-Authenticate header value: " + wwwAuthHeader);
}

// Extract parts
String realm = matcher.group(1);
String service = matcher.group(2);
String scope = matcher.group(3);
String error = matcher.group(5);

LOG.debug("WWW-Authenticate header: realm={}, service={}, scope={}, error={}", realm, service, scope, error);

URI uri = URI.create(realm + "?scope=" + scope + "&service=" + service);

// Perform the request to get the token
Map<String, String> headers = new HashMap<>();
String authHeader = provider.getAuthHeader(containerRef);
if (authHeader != null) {
headers.put(Const.AUTHORIZATION_HEADER, authHeader);
}
OrasHttpClient.ResponseWrapper<String> responseWrapper = client.get(uri, headers);

// Log the response
LOG.debug(
"Response: {}",
responseWrapper
.response()
.replaceAll("\"token\"\\s*:\\s*\"([A-Za-z0-9\\-_\\.]+)\"", "\"token\":\"<redacted>\"")
.replaceAll(
"\"access_token\"\\s*:\\s*\"([A-Za-z0-9\\-_\\.]+)\"",
"\"access_token\":\"<redacted>\""));
LOG.debug(
"Headers: {}",
responseWrapper.headers().entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> Const.AUTHORIZATION_HEADER.equalsIgnoreCase(entry.getKey())
? "<redacted" // Replace value with ****
: entry.getValue())));

this.token = JsonUtils.fromJson(responseWrapper.response(), TokenResponse.class);
return this;
public HttpClient.@Nullable TokenResponse getToken() {
return token;

Check warning on line 56 in src/main/java/land/oras/auth/BearerTokenProvider.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/land/oras/auth/BearerTokenProvider.java#L56

Added line #L56 was not covered by tests
}

/**
* Get the token
* @return The token
* Set the token
* @param token The token
*/
public @Nullable TokenResponse getToken() {
return token;
public void setToken(HttpClient.TokenResponse token) {
this.token = token;

Check warning on line 64 in src/main/java/land/oras/auth/BearerTokenProvider.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/land/oras/auth/BearerTokenProvider.java#L64

Added line #L64 was not covered by tests
}

@Override
public @Nullable String getAuthHeader(ContainerRef registry) {
if (token == null) {
LOG.debug("No token available. No header will be set.");
return null;
}
return "Bearer " + token.token;
return "Bearer " + token.token();

Check warning on line 73 in src/main/java/land/oras/auth/BearerTokenProvider.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/land/oras/auth/BearerTokenProvider.java#L73

Added line #L73 was not covered by tests
}

/**
* The token response
* @param token The token
* @param access_token The access token
* @param expire_in The expire in
* @param issued_at The issued at
*/
@NullMarked
public record TokenResponse(String token, String access_token, Integer expire_in, ZonedDateTime issued_at) {}
@Override
public AuthScheme getAuthScheme() {
return AuthScheme.BEARER;

Check warning on line 78 in src/main/java/land/oras/auth/BearerTokenProvider.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/land/oras/auth/BearerTokenProvider.java#L78

Added line #L78 was not covered by tests
}
}
5 changes: 5 additions & 0 deletions src/main/java/land/oras/auth/NoAuthProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,9 @@
public String getAuthHeader(ContainerRef registry) {
return null;
}

@Override
public AuthScheme getAuthScheme() {
return AuthScheme.NONE;

Check warning on line 42 in src/main/java/land/oras/auth/NoAuthProvider.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/land/oras/auth/NoAuthProvider.java#L42

Added line #L42 was not covered by tests
}
}
4 changes: 2 additions & 2 deletions src/main/java/land/oras/exception/OrasException.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@

package land.oras.exception;

import land.oras.utils.HttpClient;
import land.oras.utils.JsonUtils;
import land.oras.utils.OrasHttpClient;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
Expand Down Expand Up @@ -60,7 +60,7 @@ public OrasException(String message) {
* New exception with a message and a response
* @param response The response
*/
public OrasException(OrasHttpClient.ResponseWrapper<String> response) {
public OrasException(HttpClient.ResponseWrapper<String> response) {
this("Response code: " + response.statusCode());
try {
this.statusCode = response.statusCode();
Expand Down
Loading
0