Skip to content

Commit

Permalink
Azure.Identity Updating TokenCredential exception model and adding fi…
Browse files Browse the repository at this point in the history
…rst level logging (#8306)

* updating TokenCredential exception model and adding first level logging

* Updates for minor PR feedback

* refactor IExtendedTokenCredential to return a struct rather than tuple

* adding tests to validate exception model

* adding event source tests and fix to success logging

* updating IExtendedTokenCredential.GetTokenAsync to return value task

* fixing null ref in DefaultAzureCredential

* removing nested square brackets from event source messages

* updating shared token cache credential account selection

* adding DefaultAzureCredential tests

* fixing SharedTokenCacheCredential tests

* removing internal exception type AggregateAuthenticationException

* stop handling OperationCancelledExceptions and minor fixes

* minor fixes to exception messages and formatting

* removing unneeded abstraction classes

* remove caching of CredentialUnavailableExceptions

* including Exception.Message instead of Exception.ToString() for ChainedTokenCredential exception messages
  • Loading branch information
schaabs authored Oct 24, 2019
1 parent 66ab925 commit acb4568
Show file tree
Hide file tree
Showing 50 changed files with 3,093 additions and 1,011 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ private async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPip
if (DateTimeOffset.UtcNow >= _refreshOn)
{
AccessToken token = async ?
await _credential.GetTokenAsync(new TokenRequestContext(_scopes), message.CancellationToken).ConfigureAwait(false) :
_credential.GetToken(new TokenRequestContext(_scopes), message.CancellationToken);
await _credential.GetTokenAsync(new TokenRequestContext(_scopes, message.Request.ClientRequestId), message.CancellationToken).ConfigureAwait(false) :
_credential.GetToken(new TokenRequestContext(_scopes, message.Request.ClientRequestId), message.CancellationToken);

_headerValue = "Bearer " + token.Token;
_refreshOn = token.ExpiresOn - TimeSpan.FromMinutes(2);
Expand Down
9 changes: 8 additions & 1 deletion sdk/core/Azure.Core/src/TokenRequestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,21 @@ public readonly struct TokenRequestContext
/// Creates a new TokenRequest with the specified scopes.
/// </summary>
/// <param name="scopes">The scopes required for the token.</param>
public TokenRequestContext(string[] scopes)
/// <param name="parentRequestId">The <see cref="Request.ClientRequestId"/> of the request requiring a token for authentication, if applicable.</param>
public TokenRequestContext(string[] scopes, string? parentRequestId = default)
{
Scopes = scopes;
ParentRequestId = parentRequestId;
}

/// <summary>
/// The scopes required for the token.
/// </summary>
public string[] Scopes { get; }

/// <summary>
/// The <see cref="Request.ClientRequestId"/> of the request requiring a token for authentication, if applicable.
/// </summary>
public string? ParentRequestId { get; }
}
}
116 changes: 18 additions & 98 deletions sdk/identity/Azure.Identity/src/AadIdentityClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,130 +17,50 @@ namespace Azure.Identity
{
internal class AadIdentityClient
{
private static readonly Lazy<AadIdentityClient> s_sharedClient = new Lazy<AadIdentityClient>(() => new AadIdentityClient(null));

private readonly TokenCredentialOptions _options;
private readonly HttpPipeline _pipeline;
private readonly ClientDiagnostics _clientDiagnostics;

private const string ClientAssertionType = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";

private const string AuthenticationRequestFailedError = "The request to the identity service failed. See inner exception for details.";
private readonly CredentialPipeline _pipeline;

protected AadIdentityClient()
{
}

public AadIdentityClient(TokenCredentialOptions options = null)
public AadIdentityClient(CredentialPipeline pipeline)
{
_options = options ?? new TokenCredentialOptions();

_pipeline = HttpPipelineBuilder.Build(_options);
_clientDiagnostics = new ClientDiagnostics(_options);
_pipeline = pipeline;
}

public static AadIdentityClient SharedClient { get { return s_sharedClient.Value; } }


public virtual async Task<AccessToken> AuthenticateAsync(string tenantId, string clientId, string clientSecret, string[] scopes, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _clientDiagnostics.CreateScope("Azure.Identity.AadIdentityClient.Authenticate");
scope.Start();
using Request request = CreateClientSecretAuthRequest(tenantId, clientId, clientSecret, scopes);

try
{
using Request request = CreateClientSecretAuthRequest(tenantId, clientId, clientSecret, scopes);
try
{
return await SendAuthRequestAsync(request, cancellationToken).ConfigureAwait(false);
}
catch (RequestFailedException ex)
{
throw new AuthenticationFailedException(AuthenticationRequestFailedError, ex);
}
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
return await SendAuthRequestAsync(request, cancellationToken).ConfigureAwait(false);
}

public virtual AccessToken Authenticate(string tenantId, string clientId, string clientSecret, string[] scopes, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _clientDiagnostics.CreateScope("Azure.Identity.AadIdentityClient.Authenticate");
scope.Start();
using Request request = CreateClientSecretAuthRequest(tenantId, clientId, clientSecret, scopes);

try
{
using Request request = CreateClientSecretAuthRequest(tenantId, clientId, clientSecret, scopes);
try
{
return SendAuthRequest(request, cancellationToken);
}
catch (RequestFailedException ex)
{
throw new AuthenticationFailedException(AuthenticationRequestFailedError, ex);
}
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
return SendAuthRequest(request, cancellationToken);
}

public virtual async Task<AccessToken> AuthenticateAsync(string tenantId, string clientId, X509Certificate2 clientCertificate, string[] scopes, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _clientDiagnostics.CreateScope("Azure.Identity.AadIdentityClient.Authenticate");
scope.Start();
using Request request = CreateClientCertificateAuthRequest(tenantId, clientId, clientCertificate, scopes);

try
{
using Request request = CreateClientCertificateAuthRequest(tenantId, clientId, clientCertificate, scopes);
try
{
return await SendAuthRequestAsync(request, cancellationToken).ConfigureAwait(false);
}
catch (RequestFailedException ex)
{
throw new AuthenticationFailedException(AuthenticationRequestFailedError, ex);
}
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
return await SendAuthRequestAsync(request, cancellationToken).ConfigureAwait(false);
}

public virtual AccessToken Authenticate(string tenantId, string clientId, X509Certificate2 clientCertificate, string[] scopes, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _clientDiagnostics.CreateScope("Azure.Identity.AadIdentityClient.Authenticate");
scope.Start();
using Request request = CreateClientCertificateAuthRequest(tenantId, clientId, clientCertificate, scopes);

try
{
using Request request = CreateClientCertificateAuthRequest(tenantId, clientId, clientCertificate, scopes);
try
{
return SendAuthRequest(request, cancellationToken);
}
catch (RequestFailedException ex)
{
throw new AuthenticationFailedException(AuthenticationRequestFailedError, ex);
}
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
return SendAuthRequest(request, cancellationToken);
}

private async Task<AccessToken> SendAuthRequestAsync(Request request, CancellationToken cancellationToken)
{
Response response = await _pipeline.SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
Response response = await _pipeline.HttpPipeline.SendRequestAsync(request, cancellationToken).ConfigureAwait(false);

if (response.Status == 200 || response.Status == 201)
{
Expand All @@ -154,7 +74,7 @@ private async Task<AccessToken> SendAuthRequestAsync(Request request, Cancellati

private AccessToken SendAuthRequest(Request request, CancellationToken cancellationToken)
{
Response response = _pipeline.SendRequest(request, cancellationToken);
Response response = _pipeline.HttpPipeline.SendRequest(request, cancellationToken);

if (response.Status == 200 || response.Status == 201)
{
Expand All @@ -168,13 +88,13 @@ private AccessToken SendAuthRequest(Request request, CancellationToken cancellat

private Request CreateClientSecretAuthRequest(string tenantId, string clientId, string clientSecret, string[] scopes)
{
Request request = _pipeline.CreateRequest();
Request request = _pipeline.HttpPipeline.CreateRequest();

request.Method = RequestMethod.Post;

request.Headers.Add(HttpHeader.Common.FormUrlEncodedContentType);

request.Uri.Reset(_options.AuthorityHost);
request.Uri.Reset(_pipeline.AuthorityHost);

request.Uri.AppendPath(tenantId);

Expand All @@ -191,13 +111,13 @@ private Request CreateClientSecretAuthRequest(string tenantId, string clientId,

private Request CreateClientCertificateAuthRequest(string tenantId, string clientId, X509Certificate2 clientCertficate, string[] scopes)
{
Request request = _pipeline.CreateRequest();
Request request = _pipeline.HttpPipeline.CreateRequest();

request.Method = RequestMethod.Post;

request.Headers.Add(HttpHeader.Common.FormUrlEncodedContentType);

request.Uri.Reset(_options.AuthorityHost);
request.Uri.Reset(_pipeline.AuthorityHost);

request.Uri.AppendPath(tenantId);

Expand Down Expand Up @@ -291,7 +211,7 @@ private static AccessToken Deserialize(JsonElement json)
break;

case "expires_in":
expiresOn = DateTime.UtcNow + TimeSpan.FromSeconds(prop.Value.GetInt64());
expiresOn = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(prop.Value.GetInt64());
break;
}
}
Expand Down
24 changes: 24 additions & 0 deletions sdk/identity/Azure.Identity/src/AuthenticationFailedException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;

namespace Azure.Identity
{
Expand All @@ -28,5 +31,26 @@ public AuthenticationFailedException(string message, Exception innerException)
: base(message, innerException)
{
}

internal static AuthenticationFailedException CreateAggregateException(string message, ReadOnlyMemory<object> credentials, IList<Exception> innerExceptions)
{
StringBuilder exStr = new StringBuilder(message).AppendLine();

for (int i = 0; i < credentials.Length; i++)
{
if (innerExceptions[i] is CredentialUnavailableException)
{
exStr.AppendLine($" {credentials.Span[i].GetType().Name} is unavailable {innerExceptions[i].Message}.");
}
else
{
exStr.AppendLine($" {credentials.Span[i].GetType().Name} failed with {innerExceptions[i].Message}.");
}
}

exStr.Append("See inner exception for more detail.");

return new AuthenticationFailedException(exStr.ToString(), new AggregateException(message, innerExceptions.ToArray()));
}
}
}
Loading

0 comments on commit acb4568

Please sign in to comment.