diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs
index 1aac3dde84..438174e73b 100644
--- a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs
+++ b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs
@@ -159,7 +159,6 @@ public X509Certificate2 ClientCredentialCertificate
return null;
}
}
-
#endregion
#region Region
diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
index 8c5e702d33..3576807381 100644
--- a/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
+++ b/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
@@ -341,8 +341,6 @@ public ConfidentialClientApplicationBuilder WithGenericAuthority(string authorit
/// The builder to chain the .With methods
public ConfidentialClientApplicationBuilder WithTelemetryClient(params ITelemetryClient[] telemetryClients)
{
- ValidateUseOfExperimentalFeature("ITelemetryClient");
-
if (telemetryClients == null)
{
throw new ArgumentNullException(nameof(telemetryClients));
diff --git a/src/client/Microsoft.Identity.Client/Cache/CacheLevel.cs b/src/client/Microsoft.Identity.Client/Cache/CacheLevel.cs
new file mode 100644
index 0000000000..9ec8cda0e9
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/Cache/CacheLevel.cs
@@ -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
+{
+ ///
+ /// Identifies the type of cache that the token was read from. Cache implementations must provide this.
+ ///
+ public enum CacheLevel
+ {
+ ///
+ /// Specifies that the cache level used is None.
+ /// Token was retrieved from ESTS
+ ///
+ None = 0,
+ ///
+ /// 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.
+ ///
+ Unknown = 1,
+ ///
+ /// Specifies if the L1 cache is used.
+ ///
+ L1Cache = 2,
+ ///
+ /// Specifies if the L2 cache is used.
+ L2Cache = 3
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs b/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs
index 6489109bab..5533bfb2bc 100644
--- a/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs
+++ b/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs
@@ -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
{
@@ -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
{
@@ -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);
@@ -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;
@@ -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;
}
}
}
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs
index 966172bbba..ed8cdbaad6 100644
--- a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs
@@ -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
@@ -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 claimsToSign, bool appendDefaultClaims)
{
Certificate = certificate;
@@ -42,7 +45,7 @@ public Task AddConfidentialClientParametersAsync(
tokenEndpoint,
_claimsToSign,
_appendDefaultClaims);
-
+
string assertion = jwtToken.Sign(Certificate, _base64EncodedThumbprint, sendX5C);
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertionType, OAuth2AssertionType.JwtBearer);
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/IClientCredential.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/IClientCredential.cs
index 87b1a24184..fed446b39a 100644
--- a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/IClientCredential.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/IClientCredential.cs
@@ -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,
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SecretStringClientCredential.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SecretStringClientCredential.cs
index 0747441d88..7c707d0389 100644
--- a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SecretStringClientCredential.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SecretStringClientCredential.cs
@@ -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
@@ -14,6 +15,8 @@ internal class SecretStringClientCredential : IClientCredential
{
internal string Secret { get; }
+ public AssertionType AssertionType => AssertionType.Secret;
+
public SecretStringClientCredential(string secret)
{
Secret = secret;
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionClientCredential.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionClientCredential.cs
index 9cb133572d..b63209aa26 100644
--- a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionClientCredential.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionClientCredential.cs
@@ -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
@@ -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);
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionDelegateClientCredential.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionDelegateClientCredential.cs
index 8618dac916..1f7aeed7a3 100644
--- a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionDelegateClientCredential.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionDelegateClientCredential.cs
@@ -7,6 +7,7 @@
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
{
@@ -14,6 +15,7 @@ internal class SignedAssertionDelegateClientCredential : IClientCredential
{
internal Func> _signedAssertionDelegate { get; }
internal Func> _signedAssertionWithInfoDelegate { get; }
+ public AssertionType AssertionType => AssertionType.ClientAssertion;
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public SignedAssertionDelegateClientCredential(Func> signedAssertionDelegate)
diff --git a/src/client/Microsoft.Identity.Client/Internal/RequestContext.cs b/src/client/Microsoft.Identity.Client/Internal/RequestContext.cs
index d2fd26f748..ca535075eb 100644
--- a/src/client/Microsoft.Identity.Client/Internal/RequestContext.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/RequestContext.cs
@@ -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
diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs
index 18ee59ccd0..df1a0c1d24 100644
--- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs
@@ -104,13 +104,14 @@ public async Task 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
@@ -120,12 +121,27 @@ public async Task 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());
}
}
@@ -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))
@@ -193,6 +263,7 @@ 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);
@@ -200,6 +271,32 @@ private ApiEvent InitializeApiEvent(string accountId)
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 CacheTokenResponseAndCreateAuthenticationResultAsync(MsalTokenResponse msalTokenResponse)
{
// developer passed in user object.
diff --git a/src/client/Microsoft.Identity.Client/MsalServiceException.cs b/src/client/Microsoft.Identity.Client/MsalServiceException.cs
index 9b1f8bbb28..14dba3a357 100644
--- a/src/client/Microsoft.Identity.Client/MsalServiceException.cs
+++ b/src/client/Microsoft.Identity.Client/MsalServiceException.cs
@@ -221,6 +221,11 @@ public HttpResponseHeaders Headers
///
internal string SubError { get; set; }
+ ///
+ /// A list of STS-specific error codes that can help in diagnostics.
+ ///
+ internal string[] ErrorCodes { get; set; }
+
///
/// As per discussion with Evo, AAD
///
diff --git a/src/client/Microsoft.Identity.Client/MsalServiceExceptionFactory.cs b/src/client/Microsoft.Identity.Client/MsalServiceExceptionFactory.cs
index 233e649170..63e4671ce0 100644
--- a/src/client/Microsoft.Identity.Client/MsalServiceExceptionFactory.cs
+++ b/src/client/Microsoft.Identity.Client/MsalServiceExceptionFactory.cs
@@ -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;
}
diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/AssertionType.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/AssertionType.cs
new file mode 100644
index 0000000000..c6e90fb234
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/TelemetryCore/AssertionType.cs
@@ -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
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/Internal/Events/ApiEvent.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/Internal/Events/ApiEvent.cs
index 646c4a9bae..50cb36934d 100644
--- a/src/client/Microsoft.Identity.Client/TelemetryCore/Internal/Events/ApiEvent.cs
+++ b/src/client/Microsoft.Identity.Client/TelemetryCore/Internal/Events/ApiEvent.cs
@@ -3,6 +3,7 @@
using System;
using Microsoft.Identity.Client.AuthScheme;
+using Microsoft.Identity.Client.Cache;
using Microsoft.Identity.Client.Region;
namespace Microsoft.Identity.Client.TelemetryCore.Internal.Events
@@ -127,9 +128,12 @@ public string TokenTypeString
get => TokenType.HasValue ? TokenType.Value.ToString("D") : null;
}
+ public AssertionType AssertionType { get; set; }
+
+ public CacheLevel CacheLevel { get; set; }
+
public static bool IsLongRunningObo(ApiIds apiId) => apiId == ApiIds.InitiateLongRunningObo || apiId == ApiIds.AcquireTokenInLongRunningObo;
public static bool IsOnBehalfOfRequest(ApiIds apiId) => apiId == ApiIds.AcquireTokenOnBehalfOf || IsLongRunningObo(apiId);
-
}
}
diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryClient/TelemetryData.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryClient/TelemetryData.cs
new file mode 100644
index 0000000000..5fd08b1af4
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryClient/TelemetryData.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Microsoft.Identity.Client.Cache;
+using Microsoft.IdentityModel.Abstractions;
+
+namespace Microsoft.Identity.Client.TelemetryCore.TelemetryClient
+{
+ ///
+ /// Stores details to log to the .
+ ///
+ public class TelemetryData
+ {
+ ///
+ /// Type of cache used. This data is captured from MSAL or Microsoft.Identity.Web to log to telemetry.
+ ///
+ public CacheLevel CacheLevel { get; set; } = CacheLevel.None;
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs
index 94ac646427..db801d17e1 100644
--- a/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs
+++ b/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs
@@ -22,17 +22,24 @@ internal static class TelemetryConstants
public const string ConfigurationUpdateEventName = "config_update";
public const string MsalVersion = "MsalVersion";
public const string RemainingLifetime = "RemainingLifetime";
- public const string PopToken = "PopToken";
+ public const string TokenType = "TokenType";
public const string TokenSource = "TokenSource";
public const string CacheInfoTelemetry = "CacheInfoTelemetry";
public const string ErrorCode = "ErrorCode";
+ public const string StsErrorCode = "StsErrorCode";
+ public const string ErrorMessage = "ErrorMessage";
public const string Duration = "Duration";
public const string Succeeded = "Succeeded";
public const string DurationInCache = "DurationInCache";
public const string DurationInHttp = "DurationInHttp";
public const string ActivityId = "ActivityId";
public const string Resource = "Resource";
+ public const string RefreshOn = "RefreshOn";
+ public const string CacheLevel = "CacheLevel";
+ public const string AssertionType = "AssertionType";
+ public const string Endpoint = "Endpoint";
+ public const string Scopes = "Scopes";
-#endregion
+ #endregion
}
}
diff --git a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
index dc4eea6fbf..ab3a2d8808 100644
--- a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
+++ b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
@@ -18,6 +18,7 @@
using Microsoft.Identity.Client.Internal.Requests;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.TelemetryCore.Internal.Events;
+using Microsoft.Identity.Client.TelemetryCore.TelemetryClient;
using Microsoft.Identity.Client.Utils;
namespace Microsoft.Identity.Client
@@ -266,8 +267,8 @@ async Task> IToke
requestParams.RequestContext.ApiEvent.DurationInCacheInMs += sw.ElapsedMilliseconds;
DumpCacheToLogs(requestParams);
-
}
+
#pragma warning disable CS0618 // Type or member is obsolete
HasStateChanged = false;
#pragma warning restore CS0618 // Type or member is obsolete
@@ -1216,7 +1217,7 @@ async Task ITokenCacheInternal.StopLongRunningOboProcessAsync(string longR
requestScopes: requestParameters.Scope,
requestTenantId: requestParameters.AuthorityManager.OriginalAuthority.TenantId,
identityLogger: requestParameters.RequestContext.Logger.IdentityLogger,
- piiLoggingEnabled: requestParameters.RequestContext.Logger.PiiLoggingEnabled);
+ piiLoggingEnabled: requestParameters.RequestContext.Logger.PiiLoggingEnabled);
await tokenCacheInternal.OnAfterAccessAsync(args).ConfigureAwait(false);
}
@@ -1301,7 +1302,7 @@ async Task ITokenCacheInternal.RemoveAccountAsync(IAccount account, Authenticati
requestScopes: requestParameters.Scope,
requestTenantId: requestParameters.AuthorityManager.OriginalAuthority.TenantId,
identityLogger: requestParameters.RequestContext.Logger.IdentityLogger,
- piiLoggingEnabled: requestParameters.RequestContext.Logger.PiiLoggingEnabled);
+ piiLoggingEnabled: requestParameters.RequestContext.Logger.PiiLoggingEnabled);
await tokenCacheInternal.OnAfterAccessAsync(args).ConfigureAwait(false);
}
diff --git a/src/client/Microsoft.Identity.Client/TokenCacheNotificationArgs.cs b/src/client/Microsoft.Identity.Client/TokenCacheNotificationArgs.cs
index f8db180c66..10b7e3de45 100644
--- a/src/client/Microsoft.Identity.Client/TokenCacheNotificationArgs.cs
+++ b/src/client/Microsoft.Identity.Client/TokenCacheNotificationArgs.cs
@@ -5,6 +5,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Threading;
+using Microsoft.Identity.Client.TelemetryCore.TelemetryClient;
using Microsoft.IdentityModel.Abstractions;
namespace Microsoft.Identity.Client
@@ -127,8 +128,8 @@ public TokenCacheNotificationArgs( // only use this constructor in product co
IEnumerable requestScopes,
string requestTenantId,
IIdentityLogger identityLogger,
- bool piiLoggingEnabled)
-
+ bool piiLoggingEnabled,
+ TelemetryData telemetryData = null)
{
TokenCache = tokenCache;
ClientId = clientId;
@@ -144,6 +145,7 @@ public TokenCacheNotificationArgs( // only use this constructor in product co
SuggestedCacheExpiry = suggestedCacheExpiry;
IdentityLogger = identityLogger;
PiiLoggingEnabled = piiLoggingEnabled;
+ TelemetryData = telemetryData?? new TelemetryData();
}
///
@@ -248,5 +250,10 @@ public TokenCacheNotificationArgs( // only use this constructor in product co
/// Boolean used to determine if Personally Identifiable Information (PII) logging is enabled.
///
public bool PiiLoggingEnabled { get; }
+
+ ///
+ /// Cache Details contains the details of L1/ L2 cache for telemetry logging.
+ ///
+ public TelemetryData TelemetryData { get; }
}
}
diff --git a/tests/Microsoft.Identity.Test.Common/Core/Helpers/RSACertificatePopCryptoProvider.cs b/tests/Microsoft.Identity.Test.Common/Core/Helpers/RSACertificatePopCryptoProvider.cs
new file mode 100644
index 0000000000..4a3654b2c2
--- /dev/null
+++ b/tests/Microsoft.Identity.Test.Common/Core/Helpers/RSACertificatePopCryptoProvider.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography.X509Certificates;
+using System.Security.Cryptography;
+using System.Text;
+using Microsoft.Identity.Client.AuthScheme.PoP;
+using Microsoft.Identity.Client.Utils;
+
+namespace Microsoft.Identity.Test.Common.Core.Helpers
+{
+ public class RSACertificatePopCryptoProvider : IPoPCryptoProvider
+ {
+ private readonly X509Certificate2 _cert;
+
+ public RSACertificatePopCryptoProvider(X509Certificate2 cert)
+ {
+ _cert = cert ?? throw new ArgumentNullException(nameof(cert));
+
+ RSA provider = _cert.GetRSAPublicKey();
+ RSAParameters publicKeyParams = provider.ExportParameters(false);
+ CannonicalPublicKeyJwk = ComputeCannonicalJwk(publicKeyParams);
+ }
+
+ public byte[] Sign(byte[] payload)
+ {
+ using (RSA key = _cert.GetRSAPrivateKey())
+ {
+ return key.SignData(
+ payload,
+ HashAlgorithmName.SHA256,
+ RSASignaturePadding.Pkcs1);
+ }
+ }
+
+ public string CannonicalPublicKeyJwk { get; }
+
+ public string CryptographicAlgorithm { get => "RS256"; }
+
+ ///
+ /// Creates the canonical representation of the JWK. See https://tools.ietf.org/html/rfc7638#section-3
+ /// The number of parameters as well as the lexicographic order is important, as this string will be hashed to get a thumbprint
+ ///
+ private static string ComputeCannonicalJwk(RSAParameters rsaPublicKey)
+ {
+ return $@"{{""e"":""{Base64UrlHelpers.Encode(rsaPublicKey.Exponent)}"",""kty"":""RSA"",""n"":""{Base64UrlHelpers.Encode(rsaPublicKey.Modulus)}""}}";
+ }
+ }
+}
diff --git a/tests/Microsoft.Identity.Test.Common/Core/Helpers/TestTelemetryClient.cs b/tests/Microsoft.Identity.Test.Common/Core/Helpers/TestTelemetryClient.cs
new file mode 100644
index 0000000000..50a2f25f29
--- /dev/null
+++ b/tests/Microsoft.Identity.Test.Common/Core/Helpers/TestTelemetryClient.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.Identity.Client.TelemetryCore;
+using Microsoft.Identity.Client.TelemetryCore.TelemetryClient;
+using Microsoft.IdentityModel.Abstractions;
+
+namespace Microsoft.Identity.Test.Common.Core.Helpers
+{
+ internal class TestTelemetryClient : ITelemetryClient
+ {
+ public MsalTelemetryEventDetails TestTelemetryEventDetails { get; set; }
+
+ public TestTelemetryClient(string clientId)
+ {
+ ClientId = clientId;
+ }
+
+ public string ClientId { get; set; }
+
+ public void Initialize()
+ {
+
+ }
+
+ public bool IsEnabled()
+ {
+ return true;
+ }
+
+ public bool IsEnabled(string eventName)
+ {
+ return TelemetryConstants.AcquireTokenEventName.Equals(eventName);
+ }
+
+ public void TrackEvent(TelemetryEventDetails eventDetails)
+ {
+ TestTelemetryEventDetails = (MsalTelemetryEventDetails)eventDetails;
+ }
+
+ public void TrackEvent(string eventName, IDictionary stringProperties = null, IDictionary longProperties = null, IDictionary boolProperties = null, IDictionary dateTimeProperties = null, IDictionary doubleProperties = null, IDictionary guidProperties = null)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/PoPTests.cs b/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/PoPTests.cs
index fc7a2f9a4e..fa74861907 100644
--- a/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/PoPTests.cs
+++ b/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/PoPTests.cs
@@ -29,6 +29,10 @@
using Microsoft.IdentityModel.Tokens;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Identity.Test.Common.Core.Helpers;
+using Microsoft.IdentityModel.Abstractions;
+using Microsoft.Identity.Client.TelemetryCore.TelemetryClient;
+using Microsoft.Identity.Client.AuthScheme;
+using Microsoft.Identity.Client.TelemetryCore;
namespace Microsoft.Identity.Test.Integration.HeadlessTests
{
@@ -258,11 +262,13 @@ await VerifyPoPTokenAsync(
[TestMethod]
public async Task PopTestWithRSAAsync()
{
+ var telemetryClient = new TestTelemetryClient(TestConstants.ClientId);
var confidentialApp = ConfidentialClientApplicationBuilder
.Create(PublicCloudConfidentialClientID)
.WithExperimentalFeatures()
.WithAuthority(PublicCloudTestAuthority)
.WithClientSecret(s_publicCloudCcaSecret)
+ .WithTelemetryClient(telemetryClient)
.Build();
//RSA provider
@@ -281,6 +287,10 @@ await VerifyPoPTokenAsync(
ProtectedUrl,
HttpMethod.Get,
result).ConfigureAwait(false);
+
+ MsalTelemetryEventDetails eventDetails = telemetryClient.TestTelemetryEventDetails;
+ Assert.IsNotNull(eventDetails);
+ Assert.AreEqual(Convert.ToInt64(TokenType.Pop), eventDetails.Properties[TelemetryConstants.TokenType]);
}
[TestMethod]
@@ -590,42 +600,4 @@ private static string CreateRsaKeyId(RSAParameters rsaParameters)
return Base64UrlEncoder.Encode(sha2.ComputeHash(kidBytes));
}
}
-
- public class RSACertificatePopCryptoProvider : IPoPCryptoProvider
- {
- private readonly X509Certificate2 _cert;
-
- public RSACertificatePopCryptoProvider(X509Certificate2 cert)
- {
- _cert = cert ?? throw new ArgumentNullException(nameof(cert));
-
- RSA provider = _cert.GetRSAPublicKey();
- RSAParameters publicKeyParams = provider.ExportParameters(false);
- CannonicalPublicKeyJwk = ComputeCannonicalJwk(publicKeyParams);
- }
-
- public byte[] Sign(byte[] payload)
- {
- using (RSA key = _cert.GetRSAPrivateKey())
- {
- return key.SignData(
- payload,
- HashAlgorithmName.SHA256,
- RSASignaturePadding.Pkcs1);
- }
- }
-
- public string CannonicalPublicKeyJwk { get; }
-
- public string CryptographicAlgorithm { get => "RS256"; }
-
- ///
- /// Creates the canonical representation of the JWK. See https://tools.ietf.org/html/rfc7638#section-3
- /// The number of parameters as well as the lexicographic order is important, as this string will be hashed to get a thumbprint
- ///
- private static string ComputeCannonicalJwk(RSAParameters rsaPublicKey)
- {
- return $@"{{""e"":""{Base64UrlHelpers.Encode(rsaPublicKey.Exponent)}"",""kty"":""RSA"",""n"":""{Base64UrlHelpers.Encode(rsaPublicKey.Modulus)}""}}";
- }
- }
}
diff --git a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/AppServiceTests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/AppServiceTests.cs
index 052e5e7113..fb2b3e25ad 100644
--- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/AppServiceTests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/AppServiceTests.cs
@@ -16,7 +16,6 @@
namespace Microsoft.Identity.Test.Unit.ManagedIdentityTests
{
-
[TestClass]
public class AppServiceTests : TestBase
{
diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs
index 3d5dde89f4..0131a1e40f 100644
--- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs
@@ -29,7 +29,7 @@ namespace Microsoft.Identity.Test.Unit.PublicApiTests
[TestClass]
[DeploymentItem(@"Resources\valid.crtfile")]
[DeploymentItem("Resources\\OpenidConfiguration-QueryParams-B2C.json")]
- public class ConfidentialClientApplicationTests
+ public class ConfidentialClientApplicationTests : TestBase
{
private byte[] _serializedCache;
@@ -1913,8 +1913,6 @@ public async Task ValidateAppTokenProviderAsync()
Assert.AreEqual(4, callbackInvoked);
}
-
-
private AppTokenProviderResult GetAppTokenProviderResult(string differentScopesForAt = "", long? refreshIn = 1000)
{
var token = new AppTokenProviderResult();
diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/TelemetryClientTests.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/TelemetryClientTests.cs
index a47a46e686..6cf9003070 100644
--- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/TelemetryClientTests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/TelemetryClientTests.cs
@@ -4,16 +4,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
+using Microsoft.Identity.Client.AppConfig;
+using Microsoft.Identity.Client.AuthScheme;
+using Microsoft.Identity.Client.Cache;
+using Microsoft.Identity.Client.Extensibility;
+using Microsoft.Identity.Client.ManagedIdentity;
using Microsoft.Identity.Client.TelemetryCore;
using Microsoft.Identity.Client.TelemetryCore.TelemetryClient;
+using Microsoft.Identity.Client.Utils;
using Microsoft.Identity.Test.Common.Core.Helpers;
using Microsoft.Identity.Test.Common.Core.Mocks;
using Microsoft.Identity.Test.Common.Mocks;
+using Microsoft.Identity.Test.Unit.TelemetryTests;
using Microsoft.IdentityModel.Abstractions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NSubstitute;
+using static Microsoft.Identity.Test.Common.Core.Helpers.ManagedIdentityTestUtil;
namespace Microsoft.Identity.Test.Unit.PublicApiTests
{
@@ -22,12 +34,12 @@ public class TelemetryClientTests : TestBase
{
private MockHttpAndServiceBundle _harness;
private ConfidentialClientApplication _cca;
- private TelemetryClient _telemetryClient;
+ private TestTelemetryClient _telemetryClient;
[TestInitialize]
public override void TestInitialize()
{
- _telemetryClient = new TelemetryClient(TestConstants.ClientId);
+ _telemetryClient = new TestTelemetryClient(TestConstants.ClientId);
base.TestInitialize();
}
@@ -37,18 +49,6 @@ public override void TestCleanup()
base.TestCleanup();
}
- [TestMethod]
- public void TelemetryClientExperimental()
- {
- var e = AssertException.Throws(() => ConfidentialClientApplicationBuilder
- .Create(TestConstants.ClientId)
- .WithClientSecret("secret")
- .WithTelemetryClient(_telemetryClient)
- .Build());
-
- Assert.AreEqual(MsalError.ExperimentalFeature, e.ErrorCode);
- }
-
[TestMethod]
public void TelemetryClientListNull()
{
@@ -88,17 +88,17 @@ public void TelemetryClientNoArg()
Assert.IsNotNull(cca);
}
- [TestMethod]
+ [TestMethod]
public async Task AcquireTokenSuccessfulTelemetryTestAsync()
{
using (_harness = CreateTestHarness())
{
_harness.HttpManager.AddInstanceDiscoveryMockHandler();
-
+
CreateApplication();
_harness.HttpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage();
- // Acquire token interactively
+ // Acquire token for client with scope
var result = await _cca.AcquireTokenForClient(TestConstants.s_scope)
.WithAuthority(TestConstants.AuthorityUtidTenant)
.ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
@@ -106,7 +106,17 @@ public async Task AcquireTokenSuccessfulTelemetryTestAsync()
Assert.IsNotNull(result);
MsalTelemetryEventDetails eventDetails = _telemetryClient.TestTelemetryEventDetails;
- AssertLoggedTelemetry(result, eventDetails, TokenSource.IdentityProvider, CacheRefreshReason.NoCachedAccessToken);
+ AssertLoggedTelemetry(
+ result,
+ eventDetails,
+ TokenSource.IdentityProvider,
+ CacheRefreshReason.NoCachedAccessToken,
+ AssertionType.Secret,
+ TestConstants.AuthorityUtidTenant,
+ TokenType.Bearer,
+ CacheLevel.None,
+ TestConstants.s_scope.AsSingleString(),
+ null);
// Acquire token silently
var account = (await _cca.GetAccountsAsync().ConfigureAwait(false)).Single();
@@ -116,7 +126,283 @@ public async Task AcquireTokenSuccessfulTelemetryTestAsync()
Assert.IsNotNull(result);
eventDetails = _telemetryClient.TestTelemetryEventDetails;
- AssertLoggedTelemetry(result, eventDetails, TokenSource.Cache, CacheRefreshReason.NotApplicable);
+ AssertLoggedTelemetry(
+ result,
+ eventDetails,
+ TokenSource.Cache,
+ CacheRefreshReason.NotApplicable,
+ AssertionType.Secret,
+ TestConstants.AuthorityUtidTenant,
+ TokenType.Bearer,
+ CacheLevel.Unknown,
+ TestConstants.s_scope.AsSingleString(),
+ null);
+
+ _harness.HttpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage();
+
+ // Acquire token forclient with resource
+ result = await _cca.AcquireTokenForClient(new[] { TestConstants.DefaultGraphScope })
+ .WithAuthority(TestConstants.AuthorityUtidTenant)
+ .ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
+
+ Assert.IsNotNull(result);
+
+ eventDetails = _telemetryClient.TestTelemetryEventDetails;
+ AssertLoggedTelemetry(
+ result,
+ eventDetails,
+ TokenSource.IdentityProvider,
+ CacheRefreshReason.NoCachedAccessToken,
+ AssertionType.Secret,
+ TestConstants.AuthorityUtidTenant,
+ TokenType.Bearer,
+ CacheLevel.None,
+ null,
+ "https://graph.microsoft.com");
+ }
+ }
+
+ [TestMethod]
+ [DataRow(new[] { "https://graph.microsoft.com/.default" }, "https://graph.microsoft.com", ".default")]
+ [DataRow(new[] { "https://graph.microsoft.com/User.Read", "https://graph.microsoft.com/Mail.Read" }, "https://graph.microsoft.com", "User.Read Mail.Read")]
+ [DataRow(new[] { "api://23c64cd8-21e4-41dd-9756-ab9e2c23f58c/access_as_user" }, "api://23c64cd8-21e4-41dd-9756-ab9e2c23f58c", "access_as_user")]
+ [DataRow(new[] { "User.Read", "Mail.Read" }, null, "User.Read Mail.Read")]
+ [DataRow(new[] { "https://sharepoint.com/scope" }, "https://sharepoint.com", "scope")]
+ [DataRow(new[] { "offline_access", "openid", "profile" }, null, "offline_access openid profile")]
+ [DataRow(new[] { "https://graph.microsoft.com/.default", "User.Read" }, "https://graph.microsoft.com", ".default User.Read")]
+ public async Task AcquireTokenSuccessfulTelemetryTestForScopesAsync(IEnumerable input, string expectedResource, string expectedScope)
+ {
+ using (_harness = CreateTestHarness())
+ {
+ _harness.HttpManager.AddInstanceDiscoveryMockHandler();
+
+ CreateApplication();
+ _harness.HttpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage();
+
+ // Acquire token for client with scope
+ var result = await _cca.AcquireTokenForClient(input)
+ .WithAuthority(TestConstants.AuthorityUtidTenant)
+ .ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
+
+ Assert.IsNotNull(result);
+
+ MsalTelemetryEventDetails eventDetails = _telemetryClient.TestTelemetryEventDetails;
+ AssertLoggedTelemetry(
+ result,
+ eventDetails,
+ TokenSource.IdentityProvider,
+ CacheRefreshReason.NoCachedAccessToken,
+ AssertionType.Secret,
+ TestConstants.AuthorityUtidTenant,
+ TokenType.Bearer,
+ CacheLevel.None,
+ expectedScope,
+ expectedResource);
+ }
+ }
+
+ [TestMethod]
+ [DataRow(1)]
+ [DataRow(2)]
+ [DataRow(3)]
+ [DataRow(4)]
+ [DataRow(5)]
+ public async Task AcquireTokenAssertionTypeTelemetryTestAsync(int assertionType)
+ {
+ using (_harness = CreateTestHarness())
+ {
+ _harness.HttpManager.AddInstanceDiscoveryMockHandler();
+
+ CreateApplication((AssertionType)assertionType);
+ if (assertionType != 5)
+ {
+ _harness.HttpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage();
+ }
+
+ var result = await _cca.AcquireTokenForClient(TestConstants.s_scope)
+ .WithAuthority(TestConstants.AuthorityUtidTenant)
+ .ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
+
+ Assert.IsNotNull(result);
+
+ MsalTelemetryEventDetails eventDetails = _telemetryClient.TestTelemetryEventDetails;
+ AssertLoggedTelemetry(
+ result,
+ eventDetails,
+ TokenSource.IdentityProvider,
+ CacheRefreshReason.NoCachedAccessToken,
+ (AssertionType)assertionType,
+ TestConstants.AuthorityUtidTenant,
+ TokenType.Bearer,
+ CacheLevel.None,
+ TestConstants.s_scope.AsSingleString(),
+ null);
+ }
+ }
+
+ [TestMethod]
+ public async Task AcquireTokenCacheTelemetryTestAsync()
+ {
+ using (_harness = CreateTestHarness())
+ {
+ //Create app
+ CacheLevel cacheLevel = CacheLevel.L1Cache;
+ _harness.HttpManager.AddInstanceDiscoveryMockHandler();
+ CreateApplication();
+
+ _harness.HttpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage();
+
+ //Configure cache
+ _cca.AppTokenCache.SetBeforeAccess((args) =>
+ {
+ args.TelemetryData.CacheLevel = cacheLevel;
+ });
+
+ _cca.AppTokenCache.SetAfterAccess((args) =>
+ {
+ args.TelemetryData.CacheLevel = cacheLevel;
+ });
+
+ //Acquire Token
+ var result = await _cca.AcquireTokenForClient(TestConstants.s_scope)
+ .WithAuthority(TestConstants.AuthorityUtidTenant)
+ .ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
+
+ Assert.IsNotNull(result);
+
+ MsalTelemetryEventDetails eventDetails = _telemetryClient.TestTelemetryEventDetails;
+
+ //Validate telemetry
+ AssertLoggedTelemetry(
+ result,
+ eventDetails,
+ TokenSource.IdentityProvider,
+ CacheRefreshReason.NoCachedAccessToken,
+ AssertionType.Secret,
+ TestConstants.AuthorityUtidTenant,
+ TokenType.Bearer,
+ CacheLevel.None,
+ TestConstants.s_scope.AsSingleString(),
+ null);
+
+ //Update cache type
+ cacheLevel = CacheLevel.L1Cache;
+
+ //Acquire Token
+ result = await _cca.AcquireTokenForClient(TestConstants.s_scope)
+ .WithAuthority(TestConstants.AuthorityUtidTenant)
+ .ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
+
+ Assert.IsNotNull(result);
+
+ eventDetails = _telemetryClient.TestTelemetryEventDetails;
+
+ //Validate telemetry
+ AssertLoggedTelemetry(
+ result,
+ eventDetails,
+ TokenSource.Cache,
+ CacheRefreshReason.NotApplicable,
+ AssertionType.Secret,
+ TestConstants.AuthorityUtidTenant,
+ TokenType.Bearer,
+ CacheLevel.L1Cache,
+ TestConstants.s_scope.AsSingleString(),
+ null);
+
+ //Update cache type again
+ cacheLevel = CacheLevel.L2Cache;
+
+ //Acquire Token
+ result = await _cca.AcquireTokenForClient(TestConstants.s_scope)
+ .WithAuthority(TestConstants.AuthorityUtidTenant)
+ .ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
+
+ Assert.IsNotNull(result);
+
+ eventDetails = _telemetryClient.TestTelemetryEventDetails;
+
+ //Validate telemetry
+ AssertLoggedTelemetry(
+ result,
+ eventDetails,
+ TokenSource.Cache,
+ CacheRefreshReason.NotApplicable,
+ AssertionType.Secret,
+ TestConstants.AuthorityUtidTenant,
+ TokenType.Bearer,
+ CacheLevel.L2Cache,
+ TestConstants.s_scope.AsSingleString(),
+ null);
+
+ //Simulate the cache not providing a value
+ cacheLevel = CacheLevel.None;
+
+ //Acquire Token
+ result = await _cca.AcquireTokenForClient(TestConstants.s_scope)
+ .WithAuthority(TestConstants.AuthorityUtidTenant)
+ .ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
+
+ Assert.IsNotNull(result);
+
+ eventDetails = _telemetryClient.TestTelemetryEventDetails;
+
+ //Validate telemetry
+ AssertLoggedTelemetry(
+ result,
+ eventDetails,
+ TokenSource.Cache,
+ CacheRefreshReason.NotApplicable,
+ AssertionType.Secret,
+ TestConstants.AuthorityUtidTenant,
+ TokenType.Bearer,
+ CacheLevel.Unknown,
+ TestConstants.s_scope.AsSingleString(),
+ null);
+ }
+ }
+
+ [TestMethod]
+ public async Task AcquireTokenWithMSITelemetryTestAsync()
+ {
+ using (new EnvVariableContext())
+ using (_harness = CreateTestHarness())
+ {
+ string endpoint = "http://localhost:40342/metadata/identity/oauth2/token";
+ string resource = "https://management.azure.com";
+
+ Environment.SetEnvironmentVariable("MSI_ENDPOINT", endpoint);
+
+ var mia = ManagedIdentityApplicationBuilder
+ .Create(ManagedIdentityId.SystemAssigned)
+ .WithExperimentalFeatures()
+ .WithHttpManager(_harness.HttpManager)
+ .WithTelemetryClient(_telemetryClient)
+ .Build();
+
+ _harness.HttpManager.AddManagedIdentityMockHandler(
+ endpoint,
+ resource,
+ MockHelpers.GetMsiSuccessfulResponse(),
+ ManagedIdentitySource.CloudShell);
+
+ var result = await mia.AcquireTokenForManagedIdentity(resource)
+ .ExecuteAsync().ConfigureAwait(false);
+
+ Assert.IsNotNull(result);
+
+ MsalTelemetryEventDetails eventDetails = _telemetryClient.TestTelemetryEventDetails;
+ AssertLoggedTelemetry(
+ result,
+ eventDetails,
+ TokenSource.IdentityProvider,
+ CacheRefreshReason.NoCachedAccessToken,
+ AssertionType.ManagedIdentity,
+ "https://login.microsoftonline.com/managed_identity/",
+ TokenType.Bearer,
+ CacheLevel.None,
+ null,
+ resource);
}
}
@@ -130,6 +416,7 @@ public async Task AcquireTokenUnSuccessfulTelemetryTestAsync()
CreateApplication();
_harness.HttpManager.AddTokenResponse(TokenResponseType.InvalidClient);
+ //Test for MsalServiceException
MsalServiceException ex = await AssertException.TaskThrowsAsync(
() => _cca.AcquireTokenForClient(TestConstants.s_scope)
.WithAuthority(TestConstants.AuthorityUtidTenant)
@@ -140,69 +427,149 @@ public async Task AcquireTokenUnSuccessfulTelemetryTestAsync()
MsalTelemetryEventDetails eventDetails = _telemetryClient.TestTelemetryEventDetails;
Assert.AreEqual(ex.ErrorCode, eventDetails.Properties[TelemetryConstants.ErrorCode]);
+ Assert.AreEqual(ex.Message, eventDetails.Properties[TelemetryConstants.ErrorMessage]);
+ Assert.AreEqual(ex.ErrorCodes.AsSingleString(), eventDetails.Properties[TelemetryConstants.StsErrorCode]);
+ Assert.IsFalse((bool?)eventDetails.Properties[TelemetryConstants.Succeeded]);
+
+ //Test for MsalClientException
+ _harness.HttpManager.AddTokenResponse(TokenResponseType.InvalidClient);
+
+ MsalClientException exClient = await AssertException.TaskThrowsAsync(
+ () => _cca.AcquireTokenForClient(null)
+ .WithAuthority(TestConstants.AuthorityUtidTenant)
+ .ExecuteAsync(CancellationToken.None)).ConfigureAwait(false);
+
+ Assert.IsNotNull(exClient);
+ Assert.IsNotNull(exClient.ErrorCode);
+
+ eventDetails = _telemetryClient.TestTelemetryEventDetails;
+ Assert.AreEqual(exClient.ErrorCode, eventDetails.Properties[TelemetryConstants.ErrorCode]);
+ Assert.AreEqual(exClient.Message, eventDetails.Properties[TelemetryConstants.ErrorMessage]);
Assert.IsFalse((bool?)eventDetails.Properties[TelemetryConstants.Succeeded]);
}
}
- private void AssertLoggedTelemetry(AuthenticationResult authenticationResult, MsalTelemetryEventDetails eventDetails, TokenSource tokenSource, CacheRefreshReason cacheRefreshReason)
+ [TestMethod]
+ public async Task AcquireTokenGenericErrorTelemetryTestAsync()
{
- Assert.IsNotNull(eventDetails);
- Assert.AreEqual(Convert.ToInt64(cacheRefreshReason), eventDetails.Properties[TelemetryConstants.CacheInfoTelemetry]);
- Assert.AreEqual(Convert.ToInt64(tokenSource), eventDetails.Properties[TelemetryConstants.TokenSource]);
- Assert.AreEqual(authenticationResult.AuthenticationResultMetadata.DurationTotalInMs, eventDetails.Properties[TelemetryConstants.Duration]);
- Assert.AreEqual(authenticationResult.AuthenticationResultMetadata.DurationInHttpInMs, eventDetails.Properties[TelemetryConstants.DurationInHttp]);
- Assert.AreEqual(authenticationResult.AuthenticationResultMetadata.DurationInCacheInMs, eventDetails.Properties[TelemetryConstants.DurationInCache]);
- Assert.AreEqual(authenticationResult.AuthenticationResultMetadata.DurationTotalInMs, eventDetails.Properties[TelemetryConstants.Duration]);
- }
+ IMsalHttpClientFactory factoryThatThrows = Substitute.For();
+ factoryThatThrows.When(x => x.GetHttpClient()).Do(x => { throw new SocketException(0); });
- private void CreateApplication()
- {
- _cca = ConfidentialClientApplicationBuilder
+ var cca = ConfidentialClientApplicationBuilder
.Create(TestConstants.ClientId)
.WithClientSecret(TestConstants.ClientSecret)
- .WithHttpManager(_harness.HttpManager)
+ .WithHttpClientFactory(factoryThatThrows)
.WithExperimentalFeatures()
.WithTelemetryClient(_telemetryClient)
.BuildConcrete();
- TokenCacheHelper.PopulateCache(_cca.UserTokenCacheInternal.Accessor);
+ MsalClientException exClient = await AssertException.TaskThrowsAsync(
+ () => cca.AcquireTokenForClient(null)
+ .WithAuthority(TestConstants.AuthorityUtidTenant)
+ .ExecuteAsync(CancellationToken.None)).ConfigureAwait(false);
+
+ Assert.IsNotNull(exClient);
+ Assert.IsNotNull(exClient.ErrorCode);
+
+ var eventDetails = _telemetryClient.TestTelemetryEventDetails;
+ Assert.AreEqual(exClient.ErrorCode, eventDetails.Properties[TelemetryConstants.ErrorCode]);
+ Assert.AreEqual(exClient.Message, eventDetails.Properties[TelemetryConstants.ErrorMessage]);
+ Assert.IsFalse((bool?)eventDetails.Properties[TelemetryConstants.Succeeded]);
}
- }
- internal class TelemetryClient : ITelemetryClient
- {
- public MsalTelemetryEventDetails TestTelemetryEventDetails { get; set; }
-
- public TelemetryClient(string clientId)
+ private void AssertLoggedTelemetry(
+ AuthenticationResult authenticationResult,
+ MsalTelemetryEventDetails eventDetails,
+ TokenSource tokenSource,
+ CacheRefreshReason cacheRefreshReason,
+ AssertionType assertionType,
+ string endpoint,
+ TokenType? tokenType,
+ CacheLevel cacheLevel,
+ string scopes,
+ string resource)
{
- ClientId = clientId;
- }
+ Assert.IsNotNull(eventDetails);
+ Assert.AreEqual(Convert.ToInt64(cacheRefreshReason), eventDetails.Properties[TelemetryConstants.CacheInfoTelemetry]);
+ Assert.AreEqual(Convert.ToInt64(tokenSource), eventDetails.Properties[TelemetryConstants.TokenSource]);
+ Assert.AreEqual(authenticationResult.AuthenticationResultMetadata.DurationTotalInMs, eventDetails.Properties[TelemetryConstants.Duration]);
+ Assert.AreEqual(authenticationResult.AuthenticationResultMetadata.DurationInHttpInMs, eventDetails.Properties[TelemetryConstants.DurationInHttp]);
+ Assert.AreEqual(authenticationResult.AuthenticationResultMetadata.DurationInCacheInMs, eventDetails.Properties[TelemetryConstants.DurationInCache]);
+ Assert.AreEqual(authenticationResult.AuthenticationResultMetadata.DurationTotalInMs, eventDetails.Properties[TelemetryConstants.Duration]);
+ Assert.AreEqual(Convert.ToInt64(assertionType), eventDetails.Properties[TelemetryConstants.AssertionType]);
+ Assert.AreEqual(Convert.ToInt64(tokenType), eventDetails.Properties[TelemetryConstants.TokenType]);
+ Assert.AreEqual(endpoint, eventDetails.Properties[TelemetryConstants.Endpoint]);
+ Assert.AreEqual(Convert.ToInt64(cacheLevel), eventDetails.Properties[TelemetryConstants.CacheLevel]);
- public string ClientId { get; set; }
+ if (!string.IsNullOrWhiteSpace(scopes))
+ {
+ Assert.AreEqual(scopes, eventDetails.Properties[TelemetryConstants.Scopes]);
+ }
- public void Initialize()
- {
+ if (!string.IsNullOrWhiteSpace(resource))
+ {
+ Assert.AreEqual(resource, eventDetails.Properties[TelemetryConstants.Resource]);
+ }
}
- public bool IsEnabled()
+ private void CreateApplication(AssertionType assertionType = AssertionType.Secret)
{
- return true;
- }
+ var certificate = new X509Certificate2(
+ ResourceHelper.GetTestResourceRelativePath("valid_cert.pfx"),
+ TestConstants.DefaultPassword);
- public bool IsEnabled(string eventName)
- {
- return TelemetryConstants.AcquireTokenEventName.Equals(eventName);
- }
+ switch (assertionType)
+ {
+ case AssertionType.Secret:
+ _cca = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithClientSecret(TestConstants.ClientSecret)
+ .WithHttpManager(_harness.HttpManager)
+ .WithExperimentalFeatures()
+ .WithTelemetryClient(_telemetryClient)
+ .BuildConcrete();
+ break;
+ case AssertionType.CertificateWithoutSni:
+ _cca = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithCertificate(certificate)
+ .WithHttpManager(_harness.HttpManager)
+ .WithExperimentalFeatures()
+ .WithTelemetryClient(_telemetryClient)
+ .BuildConcrete();
+ break;
+ case AssertionType.CertificateWithSni:
+ _cca = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithCertificate(certificate, true)
+ .WithHttpManager(_harness.HttpManager)
+ .WithExperimentalFeatures()
+ .WithTelemetryClient(_telemetryClient)
+ .BuildConcrete();
+ break;
+ case AssertionType.ClientAssertion:
+ _cca = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithClientAssertion(TestConstants.DefaultClientAssertion)
+ .WithHttpManager(_harness.HttpManager)
+ .WithExperimentalFeatures()
+ .WithTelemetryClient(_telemetryClient)
+ .BuildConcrete();
+ break;
+ case AssertionType.ManagedIdentity:
+ _cca = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithAppTokenProvider((AppTokenProviderParameters parameters) => { return Task.FromResult(GetAppTokenProviderResult()); })
+ .WithHttpManager(_harness.HttpManager)
+ .WithExperimentalFeatures()
+ .WithTelemetryClient(_telemetryClient)
+ .BuildConcrete();
+ break;
+ }
- public void TrackEvent(TelemetryEventDetails eventDetails)
- {
- TestTelemetryEventDetails = (MsalTelemetryEventDetails) eventDetails;
- }
- public void TrackEvent(string eventName, IDictionary stringProperties = null, IDictionary longProperties = null, IDictionary boolProperties = null, IDictionary dateTimeProperties = null, IDictionary doubleProperties = null, IDictionary guidProperties = null)
- {
- throw new NotImplementedException();
+ TokenCacheHelper.PopulateCache(_cca.UserTokenCacheInternal.Accessor);
}
}
}
diff --git a/tests/Microsoft.Identity.Test.Unit/TestBase.cs b/tests/Microsoft.Identity.Test.Unit/TestBase.cs
index ec1bb6a46b..ee45bd6fb9 100644
--- a/tests/Microsoft.Identity.Test.Unit/TestBase.cs
+++ b/tests/Microsoft.Identity.Test.Unit/TestBase.cs
@@ -5,6 +5,7 @@
using System.Diagnostics;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Core;
+using Microsoft.Identity.Client.Extensibility;
using Microsoft.Identity.Client.Internal.Broker;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Test.Common;
@@ -126,5 +127,15 @@ internal static IBroker CreateBroker(Type brokerType)
throw new NotImplementedException();
}
+
+ internal AppTokenProviderResult GetAppTokenProviderResult(string differentScopesForAt = "", long? refreshIn = 1000)
+ {
+ var token = new AppTokenProviderResult();
+ token.AccessToken = TestConstants.DefaultAccessToken + differentScopesForAt; //Used to indicate that there is a new access token for a different set of scopes
+ token.ExpiresInSeconds = 3600;
+ token.RefreshInSeconds = refreshIn;
+
+ return token;
+ }
}
}