Skip to content
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

AuthHandshakeMessageHandler: also try Basic auth when username is '<token>'. #43354

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,7 @@ internal static (string credU, string credP)? GetDockerCredentialsFromEnvironmen
if (!postResponse.IsSuccessStatusCode)
{
await postResponse.LogHttpResponseAsync(_logger, cancellationToken).ConfigureAwait(false);
//return null to try HTTP GET instead
return null;
return null; // try next method.
}
_logger.LogTrace("Received '{statuscode}'.", postResponse.StatusCode);
TokenResponse? tokenResponse = JsonSerializer.Deserialize<TokenResponse>(postResponse.Content.ReadAsStream(cancellationToken));
Expand All @@ -316,14 +315,34 @@ internal static (string credU, string credP)? GetDockerCredentialsFromEnvironmen
/// </summary>
private async Task<(AuthenticationHeaderValue, DateTimeOffset)?> TryTokenGetAsync(DockerCredentials privateRepoCreds, AuthInfo bearerAuthInfo, CancellationToken cancellationToken)
{
AuthenticationHeaderValue authHeader;
(AuthenticationHeaderValue, DateTimeOffset)? authenticationValueAndDuration;

// For the username '<token>', some registries expect Bearer auth while others expect Basic auth.
// We start by trying Bearer, and fall back to Basic.
if (privateRepoCreds.Username == "<token>")
{
authHeader = new AuthenticationHeaderValue(BearerAuthScheme, privateRepoCreds.Password);

authenticationValueAndDuration = await TryTokenGetAsync(authHeader, bearerAuthInfo, cancellationToken).ConfigureAwait(false);
if (authenticationValueAndDuration is not null)
{
return authenticationValueAndDuration;
}
}

// this doesn't seem to be called out in the spec, but actual username/password auth information should be converted into Basic auth here,
// even though the overall Scheme we're authenticating for is Bearer
var header = privateRepoCreds.Username == "<token>"
? new AuthenticationHeaderValue(BearerAuthScheme, privateRepoCreds.Password)
: new AuthenticationHeaderValue(BasicAuthScheme, Convert.ToBase64String(Encoding.ASCII.GetBytes($"{privateRepoCreds.Username}:{privateRepoCreds.Password}")));
authHeader = new AuthenticationHeaderValue(BasicAuthScheme, Convert.ToBase64String(Encoding.ASCII.GetBytes($"{privateRepoCreds.Username}:{privateRepoCreds.Password}")));
authenticationValueAndDuration = await TryTokenGetAsync(authHeader, bearerAuthInfo, cancellationToken).ConfigureAwait(false);
return authenticationValueAndDuration;
}

private async Task<(AuthenticationHeaderValue, DateTimeOffset)?> TryTokenGetAsync(AuthenticationHeaderValue authHeader, AuthInfo bearerAuthInfo, CancellationToken cancellationToken)
{
var builder = new UriBuilder(new Uri(bearerAuthInfo.Realm));

_logger.LogTrace("Attempting to authenticate on {uri} using GET.", bearerAuthInfo.Realm);
_logger.LogTrace("Attempting to authenticate on {uri} using GET with {scheme} auth.", bearerAuthInfo.Realm, authHeader.Scheme);
var queryDict = System.Web.HttpUtility.ParseQueryString("");
if (bearerAuthInfo.Service is string svc)
{
Expand All @@ -335,12 +354,13 @@ internal static (string credU, string credP)? GetDockerCredentialsFromEnvironmen
}
builder.Query = queryDict.ToString();
var message = new HttpRequestMessage(HttpMethod.Get, builder.ToString());
message.Headers.Authorization = header;
message.Headers.Authorization = authHeader;

using var tokenResponse = await base.SendAsync(message, cancellationToken).ConfigureAwait(false);
if (!tokenResponse.IsSuccessStatusCode)
{
throw new UnableToAccessRepositoryException(_registryName);
await tokenResponse.LogHttpResponseAsync(_logger, cancellationToken).ConfigureAwait(false);
return null; // try next method.
}

TokenResponse? token = JsonSerializer.Deserialize<TokenResponse>(tokenResponse.Content.ReadAsStream(cancellationToken));
Expand Down Expand Up @@ -412,7 +432,8 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
request.Headers.Authorization = authHeader;
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
return response;

throw new UnableToAccessRepositoryException(_registryName);
}
else
{
Expand Down
Loading