Skip to content

Commit

Permalink
Update Telemetry client with data outlined in spec (#3737)
Browse files Browse the repository at this point in the history
* Add cache details to token cache notification args

* Change code to make it more readable.

* Address comments

* Address comments

* Remove unused imports

* Change from dictionary to class

* Update comments and CacheUsed enum values

* Removing the enum as this can be inferred from Latencies

* Adding api event for credential type.

* Updating telemetry Data

* Adding tests for new telemetry datapoints.
Refactoring

* Adding telemetry cache test.
Adding Pop test

* Refactoring

* Refactoring

* Adding scopes, resources and error message to telemetry

* Apply suggestions from code review

Co-authored-by: Bogdan Gavril <[email protected]>
Co-authored-by: Peter M <[email protected]>

* Updating scope parsing logic.
Refactoring.

* Refactoring

* Updating telemetry data points
Refactoring.

* Test update

* Fix typo

* Test Fixes

* Apply suggestions from code review

Co-authored-by: Peter M <[email protected]>

* Adding additional test
Refactoring

---------

Co-authored-by: trwalke <[email protected]>
Co-authored-by: Travis Walker <[email protected]>
Co-authored-by: Bogdan Gavril <[email protected]>
Co-authored-by: Peter M <[email protected]>
  • Loading branch information
5 people authored May 23, 2023
1 parent e661354 commit c99a106
Show file tree
Hide file tree
Showing 26 changed files with 775 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ public X509Certificate2 ClientCredentialCertificate
return null;
}
}

#endregion

#region Region
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,6 @@ public ConfidentialClientApplicationBuilder WithGenericAuthority(string authorit
/// <returns>The builder to chain the .With methods</returns>
public ConfidentialClientApplicationBuilder WithTelemetryClient(params ITelemetryClient[] telemetryClients)
{
ValidateUseOfExperimentalFeature("ITelemetryClient");

if (telemetryClients == null)
{
throw new ArgumentNullException(nameof(telemetryClients));
Expand Down
35 changes: 35 additions & 0 deletions src/client/Microsoft.Identity.Client/Cache/CacheLevel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

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

namespace Microsoft.Identity.Client.Cache
{
/// <summary>
/// Identifies the type of cache that the token was read from. Cache implementations must provide this.
/// </summary>
public enum CacheLevel
{
/// <summary>
/// Specifies that the cache level used is None.
/// Token was retrieved from ESTS
/// </summary>
None = 0,
/// <summary>
/// Specifies that the cache level used is unknown.
/// Token was retrieved from cache but the token cache implementation didn't specify which cache level was used.
/// </summary>
Unknown = 1,
/// <summary>
/// Specifies if the L1 cache is used.
/// </summary>
L1Cache = 2,
/// <summary>
/// Specifies if the L2 cache is used.
L2Cache = 3
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.Identity.Client.Internal;
using Microsoft.Identity.Client.Internal.Requests;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.TelemetryCore.TelemetryClient;

namespace Microsoft.Identity.Client.Cache
{
Expand Down Expand Up @@ -106,6 +107,7 @@ private async Task RefreshCacheForReadOperationsAsync()
await TokenCacheInternal.Semaphore.WaitAsync(_requestParams.RequestContext.UserCancellationToken).ConfigureAwait(false);
_requestParams.RequestContext.Logger.Verbose(()=>"[Cache Session Manager] Entered cache semaphore");

TelemetryData telemetryData = new TelemetryData();
Stopwatch stopwatch = new Stopwatch();
try
{
Expand All @@ -129,7 +131,8 @@ private async Task RefreshCacheForReadOperationsAsync()
requestScopes: _requestParams.Scope,
requestTenantId: _requestParams.AuthorityManager.OriginalAuthority.TenantId,
identityLogger: _requestParams.RequestContext.Logger.IdentityLogger,
piiLoggingEnabled: _requestParams.RequestContext.Logger.PiiLoggingEnabled);
piiLoggingEnabled: _requestParams.RequestContext.Logger.PiiLoggingEnabled,
telemetryData: telemetryData);

stopwatch.Start();
await TokenCacheInternal.OnBeforeAccessAsync(args).ConfigureAwait(false);
Expand All @@ -155,7 +158,8 @@ private async Task RefreshCacheForReadOperationsAsync()
requestScopes: _requestParams.Scope,
requestTenantId: _requestParams.AuthorityManager.OriginalAuthority.TenantId,
identityLogger: _requestParams.RequestContext.Logger.IdentityLogger,
piiLoggingEnabled: _requestParams.RequestContext.Logger.PiiLoggingEnabled);
piiLoggingEnabled: _requestParams.RequestContext.Logger.PiiLoggingEnabled,
telemetryData: telemetryData);

await TokenCacheInternal.OnAfterAccessAsync(args).ConfigureAwait(false);
RequestContext.ApiEvent.DurationInCacheInMs += stopwatch.ElapsedMilliseconds;
Expand All @@ -170,6 +174,7 @@ private async Task RefreshCacheForReadOperationsAsync()
{
TokenCacheInternal.Semaphore.Release();
_requestParams.RequestContext.Logger.Verbose(()=>"[Cache Session Manager] Released cache semaphore");
RequestContext.ApiEvent.CacheLevel = telemetryData.CacheLevel;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.PlatformsCommon.Interfaces;
using Microsoft.Identity.Client.TelemetryCore;
using Microsoft.Identity.Client.Utils;

namespace Microsoft.Identity.Client.Internal.ClientCredential
Expand All @@ -19,6 +20,8 @@ internal class CertificateAndClaimsClientCredential : IClientCredential
private readonly string _base64EncodedThumbprint; // x5t
public X509Certificate2 Certificate { get; }

public AssertionType AssertionType => AssertionType.CertificateWithoutSni;

public CertificateAndClaimsClientCredential(X509Certificate2 certificate, IDictionary<string, string> claimsToSign, bool appendDefaultClaims)
{
Certificate = certificate;
Expand All @@ -42,7 +45,7 @@ public Task AddConfidentialClientParametersAsync(
tokenEndpoint,
_claimsToSign,
_appendDefaultClaims);

string assertion = jwtToken.Sign(Certificate, _base64EncodedThumbprint, sendX5C);

oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertionType, OAuth2AssertionType.JwtBearer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.PlatformsCommon.Interfaces;
using Microsoft.Identity.Client.TelemetryCore;
using Microsoft.Identity.Client.Utils;

namespace Microsoft.Identity.Client.Internal.ClientCredential
{
internal interface IClientCredential
{
AssertionType AssertionType { get; }

Task AddConfidentialClientParametersAsync(
OAuth2Client oAuth2Client,
ILoggerAdapter logger,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.PlatformsCommon.Interfaces;
using Microsoft.Identity.Client.TelemetryCore;
using Microsoft.Identity.Client.Utils;

namespace Microsoft.Identity.Client.Internal.ClientCredential
Expand All @@ -14,6 +15,8 @@ internal class SecretStringClientCredential : IClientCredential
{
internal string Secret { get; }

public AssertionType AssertionType => AssertionType.Secret;

public SecretStringClientCredential(string secret)
{
Secret = secret;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.PlatformsCommon.Interfaces;
using Microsoft.Identity.Client.TelemetryCore;
using Microsoft.Identity.Client.Utils;

namespace Microsoft.Identity.Client.Internal.ClientCredential
Expand All @@ -14,10 +15,13 @@ internal class SignedAssertionClientCredential : IClientCredential
{
private readonly string _signedAssertion;

public AssertionType AssertionType => AssertionType.ClientAssertion;

public SignedAssertionClientCredential(string signedAssertion)
{
_signedAssertion = signedAssertion;
}

public Task AddConfidentialClientParametersAsync(OAuth2Client oAuth2Client, ILoggerAdapter logger, ICryptographyManager cryptographyManager, string clientId, string tokenEndpoint, bool sendX5C, CancellationToken cancellationToken)
{
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertionType, OAuth2AssertionType.JwtBearer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.PlatformsCommon.Interfaces;
using Microsoft.Identity.Client.TelemetryCore;

namespace Microsoft.Identity.Client.Internal.ClientCredential
{
internal class SignedAssertionDelegateClientCredential : IClientCredential
{
internal Func<CancellationToken, Task<string>> _signedAssertionDelegate { get; }
internal Func<AssertionRequestOptions, Task<string>> _signedAssertionWithInfoDelegate { get; }
public AssertionType AssertionType => AssertionType.ClientAssertion;

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public SignedAssertionDelegateClientCredential(Func<CancellationToken, Task<string>> signedAssertionDelegate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Internal.Logger;
using Microsoft.Identity.Client.TelemetryCore;
using Microsoft.Identity.Client.TelemetryCore.Internal.Events;
using Microsoft.Identity.Client.TelemetryCore.TelemetryClient;
using Microsoft.IdentityModel.Abstractions;

namespace Microsoft.Identity.Client.Internal
Expand Down
105 changes: 101 additions & 4 deletions src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,14 @@ public async Task<AuthenticationResult> RunAsync(CancellationToken cancellationT
{
apiEvent.ApiErrorCode = ex.ErrorCode;
AuthenticationRequestParameters.RequestContext.Logger.ErrorPii(ex);
LogErrorTelemetryToClient(ex.ErrorCode, telemetryEventDetails, telemetryClients);
LogMsalErrorTelemetryToClient(ex, telemetryEventDetails, telemetryClients);
throw;
}
catch (Exception ex)
{
apiEvent.ApiErrorCode = ex.GetType().Name;
AuthenticationRequestParameters.RequestContext.Logger.ErrorPii(ex);
LogMsalErrorTelemetryToClient(ex, telemetryEventDetails, telemetryClients);
throw;
}
finally
Expand All @@ -120,12 +121,27 @@ public async Task<AuthenticationResult> RunAsync(CancellationToken cancellationT
}
}

private void LogErrorTelemetryToClient(string errorCode, MsalTelemetryEventDetails telemetryEventDetails, ITelemetryClient[] telemetryClients)
private void LogMsalErrorTelemetryToClient(Exception ex, MsalTelemetryEventDetails telemetryEventDetails, ITelemetryClient[] telemetryClients)
{
if (telemetryClients.HasEnabledClients(TelemetryConstants.AcquireTokenEventName))
{
telemetryEventDetails.SetProperty(TelemetryConstants.Succeeded, false);
telemetryEventDetails.SetProperty(TelemetryConstants.ErrorCode, errorCode);
telemetryEventDetails.SetProperty(TelemetryConstants.ErrorMessage, ex.Message);

if (ex is MsalClientException clientException)
{
telemetryEventDetails.SetProperty(TelemetryConstants.ErrorCode, clientException.ErrorCode);
return;
}

if (ex is MsalServiceException serviceException)
{
telemetryEventDetails.SetProperty(TelemetryConstants.ErrorCode, serviceException.ErrorCode);
telemetryEventDetails.SetProperty(TelemetryConstants.StsErrorCode, serviceException.ErrorCodes?.FirstOrDefault());
return;
}

telemetryEventDetails.SetProperty(TelemetryConstants.ErrorCode, ex.GetType().ToString());
}
}

Expand All @@ -139,12 +155,66 @@ private void LogSuccessfulTelemetryToClient(AuthenticationResult authenticationR
telemetryEventDetails.SetProperty(TelemetryConstants.DurationInCache, authenticationResult.AuthenticationResultMetadata.DurationInCacheInMs);
telemetryEventDetails.SetProperty(TelemetryConstants.DurationInHttp, authenticationResult.AuthenticationResultMetadata.DurationInHttpInMs);
telemetryEventDetails.SetProperty(TelemetryConstants.Succeeded, true);
telemetryEventDetails.SetProperty(TelemetryConstants.PopToken, authenticationResult.TokenType.Equals(Constants.PoPTokenType));
telemetryEventDetails.SetProperty(TelemetryConstants.TokenType, (int)AuthenticationRequestParameters.RequestContext.ApiEvent.TokenType);
telemetryEventDetails.SetProperty(TelemetryConstants.RemainingLifetime, (authenticationResult.ExpiresOn - DateTime.Now).TotalMilliseconds);
telemetryEventDetails.SetProperty(TelemetryConstants.ActivityId, authenticationResult.CorrelationId);

if (authenticationResult.AuthenticationResultMetadata.RefreshOn.HasValue)
{
telemetryEventDetails.SetProperty(TelemetryConstants.RefreshOn, DateTimeHelpers.DateTimeToUnixTimestampMilliseconds(authenticationResult.AuthenticationResultMetadata.RefreshOn.Value));
}
telemetryEventDetails.SetProperty(TelemetryConstants.AssertionType, (int)AuthenticationRequestParameters.RequestContext.ApiEvent.AssertionType);
telemetryEventDetails.SetProperty(TelemetryConstants.Endpoint, AuthenticationRequestParameters.Authority.AuthorityInfo.CanonicalAuthority.ToString());
telemetryEventDetails.SetProperty(TelemetryConstants.CacheLevel, (int)GetCacheLevel(authenticationResult));
ParseScopesForTelemetry(telemetryEventDetails);
}
}

private void ParseScopesForTelemetry(MsalTelemetryEventDetails telemetryEventDetails)
{
if (AuthenticationRequestParameters.Scope.Count > 0)
{
string firstScope = AuthenticationRequestParameters.Scope.First();

if (Uri.IsWellFormedUriString(firstScope, UriKind.Absolute))
{
Uri firstScopeAsUri = new Uri(firstScope);
telemetryEventDetails.SetProperty(TelemetryConstants.Resource, $"{firstScopeAsUri.Scheme}://{firstScopeAsUri.Host}");

StringBuilder stringBuilder = new StringBuilder();

foreach (string scope in AuthenticationRequestParameters.Scope)
{
var splitString = scope.Split(new[] { firstScopeAsUri.Host }, StringSplitOptions.None);
string scopeToAppend = splitString.Count() > 1 ? splitString[1].TrimStart('/') + " " : splitString.FirstOrDefault();
stringBuilder.Append(scopeToAppend);
}

telemetryEventDetails.SetProperty(TelemetryConstants.Scopes, stringBuilder.ToString().TrimEnd(' '));
}
else
{
telemetryEventDetails.SetProperty(TelemetryConstants.Scopes, AuthenticationRequestParameters.Scope.AsSingleString());
}
}
}

private CacheLevel GetCacheLevel(AuthenticationResult authenticationResult)
{
if (authenticationResult.AuthenticationResultMetadata.TokenSource == TokenSource.Cache) //Check if token source is cache
{
if (AuthenticationRequestParameters.RequestContext.ApiEvent.CacheLevel > CacheLevel.Unknown) //Check if cache has indicated which level was used
{
return AuthenticationRequestParameters.RequestContext.ApiEvent.CacheLevel;
}

//If no level was used, set to unknown
return CacheLevel.Unknown;
}

return CacheLevel.None;
}

private static void LogMetricsFromAuthResult(AuthenticationResult authenticationResult, ILoggerAdapter logger)
{
if (logger.IsLoggingEnabled(LogLevel.Always))
Expand Down Expand Up @@ -193,13 +263,40 @@ private ApiEvent InitializeApiEvent(string accountId)
apiEvent.IsLegacyCacheEnabled = AuthenticationRequestParameters.RequestContext.ServiceBundle.Config.LegacyCacheCompatibilityEnabled;
apiEvent.CacheInfo = CacheRefreshReason.NotApplicable;
apiEvent.TokenType = AuthenticationRequestParameters.AuthenticationScheme.TelemetryTokenType;
apiEvent.AssertionType = GetAssertionType();

// Give derived classes the ability to add or modify fields in the telemetry as needed.
EnrichTelemetryApiEvent(apiEvent);

return apiEvent;
}

private AssertionType GetAssertionType()
{
if (ServiceBundle.Config.IsManagedIdentity ||
ServiceBundle.Config.AppTokenProvider != null)
{
return AssertionType.ManagedIdentity;
}

if (ServiceBundle.Config.ClientCredential != null)
{
if (ServiceBundle.Config.ClientCredential.AssertionType == AssertionType.CertificateWithoutSni)
{
if (ServiceBundle.Config.SendX5C)
{
return AssertionType.CertificateWithSni;
}

return AssertionType.CertificateWithoutSni;
}

return ServiceBundle.Config.ClientCredential.AssertionType;
}

return AssertionType.None;
}

protected async Task<AuthenticationResult> CacheTokenResponseAndCreateAuthenticationResultAsync(MsalTokenResponse msalTokenResponse)
{
// developer passed in user object.
Expand Down
5 changes: 5 additions & 0 deletions src/client/Microsoft.Identity.Client/MsalServiceException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ public HttpResponseHeaders Headers
/// </remarks>
internal string SubError { get; set; }

/// <summary>
/// A list of STS-specific error codes that can help in diagnostics.
/// </summary>
internal string[] ErrorCodes { get; set; }

/// <summary>
/// As per discussion with Evo, AAD
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ internal static MsalServiceException FromHttpResponse(
ex.Claims = oAuth2Response?.Claims;
ex.CorrelationId = oAuth2Response?.CorrelationId;
ex.SubError = oAuth2Response?.SubError;
ex.ErrorCodes = oAuth2Response?.ErrorCodes;

return ex;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Identity.Client.TelemetryCore
{
internal enum AssertionType
{
None = 0,
CertificateWithoutSni = 1,
CertificateWithSni = 2,
Secret = 3,
ClientAssertion = 4,
ManagedIdentity = 5
}
}
Loading

0 comments on commit c99a106

Please sign in to comment.