From c1ecec7fa462c39395456513aab66b9587bc1b34 Mon Sep 17 00:00:00 2001 From: Neha Bhargava <61847233+neha-bhargava@users.noreply.github.com> Date: Mon, 16 Sep 2024 09:10:06 -0700 Subject: [PATCH] Add caller sdk id and version to telemetry server side and client side (#4864) * Add caller sdk id and version to telemetry server side and client side * Address comments * Address comments * Add constraints and use WithClientName and WithClientVersion APIs * Update tests to expect null for client name and client version * Update tests for telemetry * Add tests * Update src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs Co-authored-by: Tim Hannifin <87669370+TimHannMSFT@users.noreply.github.com> * Update src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs Co-authored-by: Tim Hannifin <87669370+TimHannMSFT@users.noreply.github.com> * Update src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs Co-authored-by: Tim Hannifin <87669370+TimHannMSFT@users.noreply.github.com> * Update src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs Co-authored-by: Tim Hannifin <87669370+TimHannMSFT@users.noreply.github.com> * Add caller sdk id and version tags to failed metrics as well * Update src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs Co-authored-by: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com> * Update src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs Co-authored-by: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com> * Address comments --------- Co-authored-by: Tim Hannifin <87669370+TimHannMSFT@users.noreply.github.com> Co-authored-by: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com> --- .../AbstractAcquireTokenParameterBuilder.cs | 14 -- ...aseAbstractAcquireTokenParameterBuilder.cs | 14 ++ .../AppConfig/AbstractApplicationBuilder.cs | 27 ---- .../AppConfig/ApplicationConfiguration.cs | 13 +- .../BaseAbstractApplicationBuilder.cs | 27 ++++ .../Internal/Constants.cs | 3 + .../Internal/Logger/LoggerHelper.cs | 2 +- .../Requests/ClientCredentialRequest.cs | 4 +- .../Requests/ManagedIdentityAuthRequest.cs | 4 +- .../Internal/Requests/OnBehalfOfRequest.cs | 4 +- .../Internal/Requests/RequestBase.cs | 49 +++++- .../Requests/Silent/CacheSilentStrategy.cs | 4 +- .../Internal/Requests/SilentRequestHelper.cs | 12 +- .../OAuth2/TokenClient.cs | 1 + .../OpenTelemetry/OtelInstrumentation.cs | 16 +- .../Http/HttpTelemetryManager.cs | 6 +- .../TelemetryCore/Internal/Events/ApiEvent.cs | 6 +- .../OpenTelemetry/IOtelInstrumentation.cs | 8 +- .../OpenTelemetry/NullOtelInstrumentation.cs | 8 +- .../TelemetryCore/TelemetryConstants.cs | 2 + .../ClientCredentialsTests.WithRegion.cs | 2 +- ...UsernamePasswordIntegrationTests.NetFwk.cs | 4 +- ...nfidentialClientApplicationBuilderTests.cs | 4 +- .../ManagedIdentityApplicationBuilderTests.cs | 20 ++- .../PublicClientApplicationBuilderTests.cs | 4 +- .../TelemetryTests/HttpTelemetryTests.cs | 151 ++++++++++++++---- .../OTelInstrumentationTests.cs | 46 ++++-- .../TelemetryTests/RegionalTelemetryTests.cs | 2 +- 28 files changed, 337 insertions(+), 120 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/AbstractAcquireTokenParameterBuilder.cs b/src/client/Microsoft.Identity.Client/ApiConfig/AbstractAcquireTokenParameterBuilder.cs index c6b4270170..b80e59acc5 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/AbstractAcquireTokenParameterBuilder.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/AbstractAcquireTokenParameterBuilder.cs @@ -46,20 +46,6 @@ protected T WithScopes(IEnumerable scopes) return this as T; } - /// - /// Sets Extra Query Parameters for the query string in the HTTP authentication request. - /// - /// This parameter will be appended as is to the query string in the HTTP authentication request to the authority - /// as a string of segments of the form key=value separated by an ampersand character. - /// The parameter can be null. - /// The builder to chain the .With methods. - public T WithExtraQueryParameters(Dictionary extraQueryParameters) - { - CommonParameters.ExtraQueryParameters = extraQueryParameters ?? - new Dictionary(StringComparer.OrdinalIgnoreCase); - return this as T; - } - /// /// Sets claims in the query. Use when the AAD admin has enabled conditional access. Acquiring the token normally will result in a /// with the property set. Retry the diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/BaseAbstractAcquireTokenParameterBuilder.cs b/src/client/Microsoft.Identity.Client/ApiConfig/BaseAbstractAcquireTokenParameterBuilder.cs index e611a72f58..7871a7eafc 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/BaseAbstractAcquireTokenParameterBuilder.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/BaseAbstractAcquireTokenParameterBuilder.cs @@ -82,6 +82,20 @@ public T WithCorrelationId(Guid correlationId) return (T)this; } + /// + /// Sets Extra Query Parameters for the query string in the HTTP authentication request. + /// + /// This parameter will be appended as is to the query string in the HTTP authentication request to the authority + /// as a string of segments of the form key=value separated by an ampersand character. + /// The parameter can be null. + /// The builder to chain the .With methods. + public T WithExtraQueryParameters(Dictionary extraQueryParameters) + { + CommonParameters.ExtraQueryParameters = extraQueryParameters ?? + new Dictionary(StringComparer.OrdinalIgnoreCase); + return this as T; + } + /// /// Validates the parameters of the AcquireToken operation. /// diff --git a/src/client/Microsoft.Identity.Client/AppConfig/AbstractApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/AbstractApplicationBuilder.cs index ff145f547a..11914ac144 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/AbstractApplicationBuilder.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/AbstractApplicationBuilder.cs @@ -256,28 +256,6 @@ public T WithTenantId(string tenantId) return this as T; } - /// - /// Sets the name of the calling application for telemetry purposes. - /// - /// The name of the application for telemetry purposes. - /// - public T WithClientName(string clientName) - { - Config.ClientName = GetValueIfNotEmpty(Config.ClientName, clientName); - return this as T; - } - - /// - /// Sets the version of the calling application for telemetry purposes. - /// - /// The version of the calling application for telemetry purposes. - /// - public T WithClientVersion(string clientVersion) - { - Config.ClientVersion = GetValueIfNotEmpty(Config.ClientVersion, clientVersion); - return this as T; - } - /// /// Sets application options, which can, for instance have been read from configuration files. /// See https://aka.ms/msal-net-application-configuration. @@ -643,10 +621,5 @@ public T WithB2CAuthority(string authorityUri) } #endregion - - private static string GetValueIfNotEmpty(string original, string value) - { - return string.IsNullOrWhiteSpace(value) ? original : value; - } } } diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs index 4950c80ffa..95a181a4e4 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs @@ -37,24 +37,23 @@ public ApplicationConfiguration(MsalClientType applicationType) break; } } - - public const string DefaultClientName = "UnknownClient"; - public const string DefaultClientVersion = "0.0.0.0"; // For telemetry, the ClientName of the application. - private string _clientName = DefaultClientName; + private string _clientName; public string ClientName { get => _clientName; - internal set { _clientName = string.IsNullOrWhiteSpace(value) ? DefaultClientName : value; } + internal set { _clientName = string.IsNullOrWhiteSpace(value) ? string.Empty : value; } + } // For telemetry, the ClientVersion of the application. - private string _clientVersion = DefaultClientVersion; + private string _clientVersion; public string ClientVersion { get => _clientVersion; - internal set { _clientVersion = string.IsNullOrWhiteSpace(value) ? DefaultClientVersion : value; } + internal set { _clientVersion = string.IsNullOrWhiteSpace(value) ? string.Empty : value; } + } public Func ParentActivityOrWindowFunc { get; internal set; } diff --git a/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs index e947a6d2cb..ad42a6cf88 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs @@ -204,6 +204,28 @@ public T WithExperimentalFeatures(bool enableExperimentalFeatures = true) return (T)this; } + /// + /// Sets the name of the calling SDK API for telemetry purposes. + /// + /// The name of the SDK API for telemetry purposes. + /// + public T WithClientName(string clientName) + { + Config.ClientName = GetValueIfNotEmpty(Config.ClientName, clientName); + return this as T; + } + + /// + /// Sets the version of the calling SDK for telemetry purposes. + /// + /// The version of the calling SDK for telemetry purposes. + /// + public T WithClientVersion(string clientVersion) + { + Config.ClientVersion = GetValueIfNotEmpty(Config.ClientVersion, clientVersion); + return this as T; + } + internal virtual ApplicationConfiguration BuildConfiguration() { ResolveAuthority(); @@ -295,5 +317,10 @@ internal void ValidateUseOfExperimentalFeature([System.Runtime.CompilerServices. MsalErrorMessage.ExperimentalFeature(memberName)); } } + + internal static string GetValueIfNotEmpty(string original, string value) + { + return string.IsNullOrWhiteSpace(value) ? original : value; + } } } diff --git a/src/client/Microsoft.Identity.Client/Internal/Constants.cs b/src/client/Microsoft.Identity.Client/Internal/Constants.cs index 226bf2c169..567e4261d3 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Constants.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Constants.cs @@ -45,6 +45,9 @@ internal static class Constants public const string ManagedIdentityDefaultTenant = "managed_identity"; public const string CiamAuthorityHostSuffix = ".ciamlogin.com"; + public const int CallerSdkIdMaxLength = 10; + public const int CallerSdkVersionMaxLength = 20; + public static string FormatEnterpriseRegistrationOnPremiseUri(string domain) { return $"https://enterpriseregistration.{domain}/enrollmentserver/contract"; diff --git a/src/client/Microsoft.Identity.Client/Internal/Logger/LoggerHelper.cs b/src/client/Microsoft.Identity.Client/Internal/Logger/LoggerHelper.cs index 076ca0f930..10546833a8 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Logger/LoggerHelper.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Logger/LoggerHelper.cs @@ -23,7 +23,7 @@ internal class LoggerHelper public static string GetClientInfo(string clientName, string clientVersion) { - if (!string.IsNullOrEmpty(clientName) && !ApplicationConfiguration.DefaultClientName.Equals(clientName)) + if (!string.IsNullOrEmpty(clientName)) { // space is intentional for formatting of the message if (string.IsNullOrEmpty(clientVersion)) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs index cc60b2d853..344cbdd017 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs @@ -79,7 +79,9 @@ protected override async Task ExecuteAsync(CancellationTok // Use a linked token source, in case the original cancellation token source is disposed before this background task completes. using var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); return GetAccessTokenAsync(tokenSource.Token, logger); - }, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId); + }, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId, + AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkApiId, + AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkVersion); } } catch (MsalServiceException e) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/ManagedIdentityAuthRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/ManagedIdentityAuthRequest.cs index 05122894d3..e67620adcb 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/ManagedIdentityAuthRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/ManagedIdentityAuthRequest.cs @@ -72,7 +72,9 @@ protected override async Task ExecuteAsync(CancellationTok // Use a linked token source, in case the original cancellation token source is disposed before this background task completes. using var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); return GetAccessTokenAsync(tokenSource.Token, logger); - }, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId); + }, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId, + AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkApiId, + AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkVersion); } } catch (MsalServiceException e) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/OnBehalfOfRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/OnBehalfOfRequest.cs index bb10edfacd..4fe91cba08 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/OnBehalfOfRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/OnBehalfOfRequest.cs @@ -135,7 +135,9 @@ protected override async Task ExecuteAsync(CancellationTok // Use a linked token source, in case the original cancellation token source is disposed before this background task completes. using var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); return RefreshRtOrFetchNewAccessTokenAsync(tokenSource.Token); - }, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId); + }, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId, + AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkApiId, + AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkVersion); } } diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index 450ad52739..6aee9819b0 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -22,6 +22,7 @@ using Microsoft.Identity.Client.TelemetryCore.TelemetryClient; using Microsoft.Identity.Client.TelemetryCore.OpenTelemetry; using Microsoft.Identity.Client.Internal.Broker; +using System.Runtime.ConstrainedExecution; namespace Microsoft.Identity.Client.Internal.Requests { @@ -91,7 +92,7 @@ public async Task RunAsync(CancellationToken cancellationT UpdateTelemetry(measureDurationResult.Milliseconds + measureTelemetryDurationResult.Milliseconds, apiEvent, authenticationResult); LogMetricsFromAuthResult(authenticationResult, AuthenticationRequestParameters.RequestContext.Logger); - LogSuccessTelemetryToOtel(authenticationResult, apiEvent.ApiId, measureDurationResult.Microseconds); + LogSuccessTelemetryToOtel(authenticationResult, apiEvent, measureDurationResult.Microseconds); return authenticationResult; } @@ -104,7 +105,7 @@ public async Task RunAsync(CancellationToken cancellationT } AuthenticationRequestParameters.RequestContext.Logger.ErrorPii(ex); - LogFailureTelemetryToOtel(ex.ErrorCode, apiEvent.ApiId, apiEvent.CacheInfo); + LogFailureTelemetryToOtel(ex.ErrorCode, apiEvent, apiEvent.CacheInfo); throw; } catch (Exception ex) @@ -112,30 +113,34 @@ public async Task RunAsync(CancellationToken cancellationT apiEvent.ApiErrorCode = ex.GetType().Name; AuthenticationRequestParameters.RequestContext.Logger.ErrorPii(ex); - LogFailureTelemetryToOtel(ex.GetType().Name, apiEvent.ApiId, apiEvent.CacheInfo); + LogFailureTelemetryToOtel(ex.GetType().Name, apiEvent, apiEvent.CacheInfo); throw; } } - private void LogSuccessTelemetryToOtel(AuthenticationResult authenticationResult, ApiEvent.ApiIds apiId, long durationInUs) + private void LogSuccessTelemetryToOtel(AuthenticationResult authenticationResult, ApiEvent apiEvent, long durationInUs) { // Log metrics ServiceBundle.PlatformProxy.OtelInstrumentation.LogSuccessMetrics( ServiceBundle.PlatformProxy.GetProductName(), - apiId, + apiEvent.ApiId, + apiEvent.CallerSdkApiId, + apiEvent.CallerSdkVersion, GetCacheLevel(authenticationResult), durationInUs, authenticationResult.AuthenticationResultMetadata, AuthenticationRequestParameters.RequestContext.Logger); } - private void LogFailureTelemetryToOtel(string errorCodeToLog, ApiEvent.ApiIds apiId, CacheRefreshReason cacheRefreshReason) + private void LogFailureTelemetryToOtel(string errorCodeToLog, ApiEvent apiEvent, CacheRefreshReason cacheRefreshReason) { // Log metrics ServiceBundle.PlatformProxy.OtelInstrumentation.LogFailureMetrics( ServiceBundle.PlatformProxy.GetProductName(), errorCodeToLog, - apiId, + apiEvent.ApiId, + apiEvent.CallerSdkApiId, + apiEvent.CallerSdkVersion, cacheRefreshReason); } @@ -242,12 +247,42 @@ private ApiEvent InitializeApiEvent(string accountId) apiEvent.TokenType = AuthenticationRequestParameters.AuthenticationScheme.TelemetryTokenType; apiEvent.AssertionType = GetAssertionType(); + UpdateCallerSdkDetails(apiEvent); + // Give derived classes the ability to add or modify fields in the telemetry as needed. EnrichTelemetryApiEvent(apiEvent); return apiEvent; } + private void UpdateCallerSdkDetails(ApiEvent apiEvent) + { + string callerSdkId; + string callerSdkVer; + + // Check if ExtraQueryParameters contains caller-sdk-id and caller-sdk-ver + if (AuthenticationRequestParameters.ExtraQueryParameters.TryGetValue("caller-sdk-id", out callerSdkId)) + { + AuthenticationRequestParameters.ExtraQueryParameters.Remove("caller-sdk-id"); + } + else + { + callerSdkId = AuthenticationRequestParameters.RequestContext.ServiceBundle.Config.ClientName; + } + + if (AuthenticationRequestParameters.ExtraQueryParameters.TryGetValue("caller-sdk-ver", out callerSdkVer)) + { + AuthenticationRequestParameters.ExtraQueryParameters.Remove("caller-sdk-ver"); + } + else + { + callerSdkVer = AuthenticationRequestParameters.RequestContext.ServiceBundle.Config.ClientVersion; + } + + apiEvent.CallerSdkApiId = callerSdkId == null ? null : callerSdkId.Substring(0, Math.Min(callerSdkId.Length, Constants.CallerSdkIdMaxLength)); + apiEvent.CallerSdkVersion = callerSdkVer == null ? null : callerSdkVer.Substring(0, Math.Min(callerSdkVer.Length, Constants.CallerSdkVersionMaxLength)); + } + private AssertionType GetAssertionType() { if (ServiceBundle.Config.IsManagedIdentity || diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/CacheSilentStrategy.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/CacheSilentStrategy.cs index 6f08bf28fa..e954ed2d12 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/CacheSilentStrategy.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/Silent/CacheSilentStrategy.cs @@ -98,7 +98,9 @@ public async Task ExecuteAsync(CancellationToken cancellat // Use a linked token source, in case the original cancellation token source is disposed before this background task completes. using var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); return RefreshRtOrFailAsync(tokenSource.Token); - }, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId); + }, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId, + AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkApiId, + AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkVersion); } } diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs index 1dfd1ee1ab..c75c0b153d 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs @@ -85,7 +85,9 @@ internal static void ProcessFetchInBackground( Func> fetchAction, ILoggerAdapter logger, IServiceBundle serviceBundle, - ApiEvent.ApiIds apiId) + ApiEvent.ApiIds apiId, + string callerSdkId, + string callerSdkVersion) { _ = Task.Run(async () => { @@ -95,6 +97,8 @@ internal static void ProcessFetchInBackground( serviceBundle.PlatformProxy.OtelInstrumentation.IncrementSuccessCounter( serviceBundle.PlatformProxy.GetProductName(), apiId, + callerSdkId, + callerSdkVersion, TokenSource.IdentityProvider, CacheRefreshReason.ProactivelyRefreshed, Cache.CacheLevel.None, @@ -116,6 +120,8 @@ internal static void ProcessFetchInBackground( serviceBundle.PlatformProxy.GetProductName(), ex.ErrorCode, apiId, + callerSdkId, + callerSdkVersion, CacheRefreshReason.ProactivelyRefreshed); } catch (OperationCanceledException ex) @@ -125,6 +131,8 @@ internal static void ProcessFetchInBackground( serviceBundle.PlatformProxy.GetProductName(), ex.GetType().Name, apiId, + callerSdkId, + callerSdkVersion, CacheRefreshReason.ProactivelyRefreshed); } catch (Exception ex) @@ -134,6 +142,8 @@ internal static void ProcessFetchInBackground( serviceBundle.PlatformProxy.GetProductName(), ex.GetType().Name, apiId, + callerSdkId, + callerSdkVersion, CacheRefreshReason.ProactivelyRefreshed); } }); diff --git a/src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs b/src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs index 67cf037830..cc9d8c9b46 100644 --- a/src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs +++ b/src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs @@ -216,6 +216,7 @@ private void AddClaims() // no-op if resolvedClaims is null _oAuth2Client.AddBodyParameter(OAuth2Parameter.Claims, resolvedClaims); } + private void AddExtraHttpHeaders() { if (_requestParams.ExtraHttpHeaders != null) diff --git a/src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs b/src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs index c01614de26..732fa7b347 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs @@ -92,6 +92,8 @@ public OtelInstrumentation() public void LogSuccessMetrics( string platform, ApiEvent.ApiIds apiId, + string callerSdkId, + string callerSdkVersion, CacheLevel cacheLevel, long totalDurationInUs, AuthenticationResultMetadata authResultMetadata, @@ -100,6 +102,8 @@ public void LogSuccessMetrics( IncrementSuccessCounter( platform, apiId, + callerSdkId, + callerSdkVersion, authResultMetadata.TokenSource, authResultMetadata.CacheRefreshReason, cacheLevel, @@ -151,6 +155,8 @@ public void LogSuccessMetrics( public void IncrementSuccessCounter(string platform, ApiEvent.ApiIds apiId, + string callerSdkId, + string callerSdkVersion, TokenSource tokenSource, CacheRefreshReason cacheRefreshReason, CacheLevel cacheLevel, @@ -162,6 +168,8 @@ public void IncrementSuccessCounter(string platform, new(TelemetryConstants.MsalVersion, MsalIdHelper.GetMsalVersion()), new(TelemetryConstants.Platform, platform), new(TelemetryConstants.ApiId, apiId), + new(TelemetryConstants.CallerSdkId, callerSdkId ?? string.Empty), + new(TelemetryConstants.CallerSdkVersion, callerSdkVersion ?? string.Empty), new(TelemetryConstants.TokenSource, tokenSource), new(TelemetryConstants.CacheRefreshReason, cacheRefreshReason), new(TelemetryConstants.CacheLevel, cacheLevel)); @@ -171,7 +179,9 @@ public void IncrementSuccessCounter(string platform, public void LogFailureMetrics(string platform, string errorCode, - ApiEvent.ApiIds apiId, + ApiEvent.ApiIds apiId, + string callerSdkId, + string callerSdkVersion, CacheRefreshReason cacheRefreshReason) { if (s_failureCounter.Value.Enabled) @@ -180,7 +190,9 @@ public void LogFailureMetrics(string platform, new(TelemetryConstants.MsalVersion, MsalIdHelper.GetMsalVersion()), new(TelemetryConstants.Platform, platform), new(TelemetryConstants.ErrorCode, errorCode), - new(TelemetryConstants.ApiId, apiId), + new(TelemetryConstants.ApiId, apiId), + new(TelemetryConstants.CallerSdkId, callerSdkId ?? ""), + new(TelemetryConstants.CallerSdkVersion, callerSdkVersion ?? ""), new(TelemetryConstants.CacheRefreshReason, cacheRefreshReason)); } } diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/Http/HttpTelemetryManager.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/Http/HttpTelemetryManager.cs index b1fd99cfd7..5a25683ad5 100644 --- a/src/client/Microsoft.Identity.Client/TelemetryCore/Http/HttpTelemetryManager.cs +++ b/src/client/Microsoft.Identity.Client/TelemetryCore/Http/HttpTelemetryManager.cs @@ -20,7 +20,7 @@ internal class HttpTelemetryManager : IHttpTelemetryManager { /// /// Expected format: 5|api_id,cache_info,region_used,region_autodetection,region_outcome|platform_config - /// platform_config: is_token_cache_serialized,is_legacy_cache_enabled, token_type + /// platform_config: is_token_cache_serialized,is_legacy_cache_enabled, token_type, caller_sdk_id, caller_sdk_version /// public string GetCurrentRequestHeader(ApiEvent eventInProgress) { @@ -51,6 +51,10 @@ public string GetCurrentRequestHeader(ApiEvent eventInProgress) sb.Append(TelemetryConstants.CommaDelimiter); // Token type is to indicate 1 - bearer, 2 - pop, 3 - ssh-cert, 4 - external. sb.Append(eventInProgress.TokenTypeString); + sb.Append(TelemetryConstants.CommaDelimiter); + sb.Append(eventInProgress.CallerSdkApiId); + sb.Append(TelemetryConstants.CommaDelimiter); + sb.Append(eventInProgress.CallerSdkVersion); return sb.ToString(); } 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 59ab567fc1..9a5635e76a 100644 --- a/src/client/Microsoft.Identity.Client/TelemetryCore/Internal/Events/ApiEvent.cs +++ b/src/client/Microsoft.Identity.Client/TelemetryCore/Internal/Events/ApiEvent.cs @@ -135,7 +135,11 @@ public string TokenTypeString public CacheLevel CacheLevel { get; set; } public string MsalRuntimeTelemetry { get; set; } - + + public string CallerSdkApiId { get; set; } + + public string CallerSdkVersion { 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/OpenTelemetry/IOtelInstrumentation.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/IOtelInstrumentation.cs index 45e2491001..da9918e2db 100644 --- a/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/IOtelInstrumentation.cs +++ b/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/IOtelInstrumentation.cs @@ -14,6 +14,8 @@ internal interface IOtelInstrumentation internal void LogSuccessMetrics( string platform, ApiEvent.ApiIds apiId, + string callerSdkId, + string callerSdkVersion, CacheLevel cacheLevel, long totalDurationInUs, AuthenticationResultMetadata authResultMetadata, @@ -21,6 +23,8 @@ internal void LogSuccessMetrics( internal void IncrementSuccessCounter(string platform, ApiEvent.ApiIds apiId, + string callerSdkId, + string callerSdkVersion, TokenSource tokenSource, CacheRefreshReason cacheRefreshReason, CacheLevel cacheLevel, @@ -28,7 +32,9 @@ internal void IncrementSuccessCounter(string platform, internal void LogFailureMetrics(string platform, string errorCode, - ApiEvent.ApiIds apiId, + ApiEvent.ApiIds apiId, + string callerSdkId, + string callerSdkVersion, CacheRefreshReason cacheRefreshReason); } } diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/NullOtelInstrumentation.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/NullOtelInstrumentation.cs index f51e276f5c..6f1f2ed4fa 100644 --- a/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/NullOtelInstrumentation.cs +++ b/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/NullOtelInstrumentation.cs @@ -17,6 +17,8 @@ internal class NullOtelInstrumentation : IOtelInstrumentation public void LogSuccessMetrics( string platform, ApiEvent.ApiIds apiId, + string callerSdkId, + string callerSdkVersion, CacheLevel cacheLevel, long totalDurationInUs, AuthenticationResultMetadata authResultMetadata, @@ -28,13 +30,17 @@ public void LogSuccessMetrics( public void LogFailureMetrics(string platform, string errorCode, ApiEvent.ApiIds apiId, + string callerSdkId, + string callerSdkVersion, CacheRefreshReason cacheRefreshReason) { // No op } void IOtelInstrumentation.IncrementSuccessCounter(string platform, - ApiEvent.ApiIds apiId, + ApiEvent.ApiIds apiId, + string callerSdkId, + string callerSdkVersion, TokenSource tokenSource, CacheRefreshReason cacheRefreshReason, CacheLevel cacheLevel, diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs index bbfbac7a98..bed6e7db55 100644 --- a/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs +++ b/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs @@ -44,6 +44,8 @@ internal static class TelemetryConstants public const string Platform = "Platform"; public const string ApiId = "ApiId"; public const string IsProactiveRefresh = "IsProactiveRefresh"; + public const string CallerSdkId = "CallerSdkId"; + public const string CallerSdkVersion = "CallerSdkVersion"; #endregion } diff --git a/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/ClientCredentialsTests.WithRegion.cs b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/ClientCredentialsTests.WithRegion.cs index b335b41681..069f0978e7 100644 --- a/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/ClientCredentialsTests.WithRegion.cs +++ b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/ClientCredentialsTests.WithRegion.cs @@ -67,7 +67,7 @@ public async Task AcquireTokenToRegionalEndpointAsync(bool instanceDiscoveryEnab AuthenticationResult result = await GetAuthenticationResultAsync(settings.AppScopes).ConfigureAwait(false); // regional endpoint AssertTokenSourceIsIdp(result); AssertValidHost(true, factory); - AssertTelemetry(factory, $"{TelemetryConstants.HttpTelemetrySchemaVersion}|1004,{CacheRefreshReason.NoCachedAccessToken:D},centralus,3,4|0,1,1"); + AssertTelemetry(factory, $"{TelemetryConstants.HttpTelemetrySchemaVersion}|1004,{CacheRefreshReason.NoCachedAccessToken:D},centralus,3,4|0,1,1,,"); Assert.AreEqual( $"https://{RegionalHost}/{settings.TenantId}/oauth2/v2.0/token", result.AuthenticationResultMetadata.TokenEndpoint); diff --git a/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/UsernamePasswordIntegrationTests.NetFwk.cs b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/UsernamePasswordIntegrationTests.NetFwk.cs index d4d1f7f0b8..59288211b4 100644 --- a/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/UsernamePasswordIntegrationTests.NetFwk.cs +++ b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/UsernamePasswordIntegrationTests.NetFwk.cs @@ -34,8 +34,8 @@ public class UsernamePasswordIntegrationTests // HTTP Telemetry Constants private static Guid CorrelationId = new Guid("ad8c894a-557f-48c0-b045-c129590c344e"); - private readonly string XClientCurrentTelemetryROPC = $"{TelemetryConstants.HttpTelemetrySchemaVersion}|1003,{CacheRefreshReason.NotApplicable:D},,,|0,1,1"; - private readonly string XClientCurrentTelemetryROPCFailure = $"{TelemetryConstants.HttpTelemetrySchemaVersion}|1003,{CacheRefreshReason.NotApplicable:D},,,|0,1,1"; + private readonly string XClientCurrentTelemetryROPC = $"{TelemetryConstants.HttpTelemetrySchemaVersion}|1003,{CacheRefreshReason.NotApplicable:D},,,|0,1,1,,"; + private readonly string XClientCurrentTelemetryROPCFailure = $"{TelemetryConstants.HttpTelemetrySchemaVersion}|1003,{CacheRefreshReason.NotApplicable:D},,,|0,1,1,,"; private const string ApiIdAndCorrelationIdSection = "1003,ad8c894a-557f-48c0-b045-c129590c344e"; private const string InvalidGrantError = "invalid_grant"; diff --git a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationBuilderTests.cs b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationBuilderTests.cs index 4183cce3ee..2c38a3d194 100644 --- a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationBuilderTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationBuilderTests.cs @@ -41,8 +41,8 @@ public void TestConstructor() // Validate Defaults Assert.AreEqual(LogLevel.Info, cca.AppConfig.LogLevel); Assert.AreEqual(TestConstants.ClientId, cca.AppConfig.ClientId); - Assert.IsNotNull(cca.AppConfig.ClientName); - Assert.IsNotNull(cca.AppConfig.ClientVersion); + Assert.IsNull(cca.AppConfig.ClientName); + Assert.IsNull(cca.AppConfig.ClientVersion); Assert.AreEqual(false, cca.AppConfig.EnablePiiLogging); Assert.IsNull(cca.AppConfig.HttpClientFactory); Assert.AreEqual(false, cca.AppConfig.IsDefaultPlatformLoggingEnabled); diff --git a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ManagedIdentityApplicationBuilderTests.cs b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ManagedIdentityApplicationBuilderTests.cs index dd999dea0f..01e26fc413 100644 --- a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ManagedIdentityApplicationBuilderTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ManagedIdentityApplicationBuilderTests.cs @@ -38,8 +38,8 @@ public void TestConstructor() Assert.AreEqual(Constants.DefaultConfidentialClientRedirectUri, mi.ServiceBundle.Config.RedirectUri); Assert.AreEqual(Constants.ManagedIdentityDefaultTenant, mi.ServiceBundle.Config.TenantId); - Assert.IsNotNull(mi.ServiceBundle.Config.ClientName); - Assert.IsNotNull(mi.ServiceBundle.Config.ClientVersion); + Assert.IsNull(mi.ServiceBundle.Config.ClientName); + Assert.IsNull(mi.ServiceBundle.Config.ClientVersion); Assert.IsNull(mi.ServiceBundle.Config.HttpClientFactory); Assert.IsNull(mi.ServiceBundle.Config.LoggingCallback); @@ -64,8 +64,8 @@ public void TestConstructor_WithCreateUserAssignedId() Assert.AreEqual(Constants.DefaultConfidentialClientRedirectUri, mi.ServiceBundle.Config.RedirectUri); Assert.AreEqual(Constants.ManagedIdentityDefaultTenant, mi.ServiceBundle.Config.TenantId); - Assert.IsNotNull(mi.ServiceBundle.Config.ClientName); - Assert.IsNotNull(mi.ServiceBundle.Config.ClientVersion); + Assert.IsNull(mi.ServiceBundle.Config.ClientName); + Assert.IsNull(mi.ServiceBundle.Config.ClientVersion); Assert.IsNotNull(mi.ServiceBundle.Config.ManagedIdentityId); Assert.AreEqual(ManagedIdentityIdType.ClientId, mi.ServiceBundle.Config.ManagedIdentityId.IdType); @@ -126,5 +126,17 @@ public void TestConstructor_WithLogging() Assert.IsNotNull(mi.ServiceBundle.Config.LoggingCallback); } + [TestMethod] + public void TestConstructor_WithClientName_WithClientVersion() + { + var mi = ManagedIdentityApplicationBuilder + .Create(ManagedIdentityId.SystemAssigned) + .WithClientName("clientName") + .WithClientVersion("clientVersion") + .BuildConcrete(); + + Assert.AreEqual("clientName", mi.ServiceBundle.Config.ClientName); + Assert.AreEqual("clientVersion", mi.ServiceBundle.Config.ClientVersion); + } } } diff --git a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/PublicClientApplicationBuilderTests.cs b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/PublicClientApplicationBuilderTests.cs index 466b3c5477..84f24a650c 100644 --- a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/PublicClientApplicationBuilderTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/PublicClientApplicationBuilderTests.cs @@ -30,8 +30,8 @@ public void TestConstructor() // Validate Defaults Assert.AreEqual(LogLevel.Info, pca.AppConfig.LogLevel); Assert.AreEqual(TestConstants.ClientId, pca.AppConfig.ClientId); - Assert.IsNotNull(pca.AppConfig.ClientName); - Assert.IsNotNull(pca.AppConfig.ClientVersion); + Assert.IsNull(pca.AppConfig.ClientName); + Assert.IsNull(pca.AppConfig.ClientVersion); Assert.IsFalse(pca.AppConfig.EnablePiiLogging); Assert.IsNull(pca.AppConfig.HttpClientFactory); Assert.IsFalse(pca.AppConfig.IsDefaultPlatformLoggingEnabled); diff --git a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/HttpTelemetryTests.cs b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/HttpTelemetryTests.cs index 2fbfd424bf..63467c0566 100644 --- a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/HttpTelemetryTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/HttpTelemetryTests.cs @@ -27,6 +27,8 @@ using NSubstitute; using NSubstitute.ExceptionExtensions; using static Microsoft.Identity.Client.TelemetryCore.Internal.Events.ApiEvent; +using System.Collections.Generic; +using Microsoft.Identity.Client.Internal; namespace Microsoft.Identity.Test.Unit.TelemetryTests { @@ -46,28 +48,19 @@ public override void TestCleanup() /// /// 1. Acquire Token Interactive successfully /// Current_request = 4 | ATI_ID, 0 | 0 - /// Last_request = 4 | 0 | | | /// /// 2. Acquire token silent with AT served from cache ... no calls to /token endpoint /// /// 3. Acquire token silent with AT not served from cache (AT expired) /// Current_request = 4 | ATS_ID, 2 | 0 - /// Last_request = 4 | 1 | | | /// /// 4. Acquire Token silent with force_refresh = true -> error invalid_client /// Sent to server - /// Current_request = 4 | ATS_ID, 1 | 0 - /// Last_request = 4 | 0 | | | - /// - /// State of client after error response is returned – (the successful silent request counter was flushed, last_request is reset, and now we add the error from step 4) - /// Last_request = 4 | 0 | ATS_ID, Corr_step_4 | invalid_client | /// /// 5. Acquire Token silent with force_refresh = true -> error interaction_required /// Sent to the server - /// Current_request = 4 | ATS_ID, 1 | 0 - /// Last_request = 4 | 0 | ATS_ID, corr_step_4 | invalid_client - /// State of client after response is returned - - /// Last_request = 4 | 0 | ATS_ID, corr_step_5 | interaction_required /// /// 6. Acquire Token interactive -> error user_cancelled (i.e. no calls to /token endpoint) /// No calls to token endpoint @@ -75,30 +68,15 @@ public override void TestCleanup() /// 7. Acquire Token interactive -> HTTP error 503 (Service Unavailable) /// /// Current_request = 4 | ATI_ID, 0 | 0 - /// Last_request = 4 | 0 | ATS_ID, corr_step_5, ATI_ID, corr_step-6, | interaction_required, - /// authentication_canceled| - /// - /// State of the client: - /// - /// Last_request = 4 | 0 | ATS_ID, corr_step_5, ATI_ID, corr_step-6, ATI-ID, corr_step-6b | interaction_required, - /// authentication_canceled, ServiceUnavailable| /// /// 8. Acquire Token interactive -> successful /// /// Sent to the server - /// Current_request = 4 | ATI_ID, 0 | 0 - /// Last_request = 4 | 0 | ATS_ID, corr_step_5, ATI_ID, corr_step-6, ATI-ID, corr_step-6b | interaction_required, - /// authentication_canceled, ServiceUnavailable | - /// - /// State of the client after response is returned - - /// Last_request = NULL /// /// 9. Acquire Token Silent with force-refresh false -> successful /// Sent to the server - /// Current_request = 4 | ATI_ID, 2 | 0 - /// Last_request = NULL - /// State of the client after response is returned - - /// Last_request = 4 | 1 | | | /// [TestMethod] public async Task TelemetryAcceptanceTestAsync() @@ -164,22 +142,17 @@ public async Task TelemetryAcceptanceTestAsync() /// /// 1. Acquire Token Interactive successfully /// Current_request = 4 |ATS_ID, 0 | , , 0, , , , 1 - /// Last_request = 4 | 0 | | | /// /// 2. Acquire token silent with AT served from cache ... no calls to /token endpoint /// /// 3. Acquire token silent with AT expired /// Current_request = 4 | ATS_ID, 3 | , , 1, , , , 1 - /// Last_request = 4 | 1 | | | /// /// 4. Acquire Token silent with refresh on /// Current_request = 4 | ATS_ID, 4 | , , 1, , , , 1 - /// Last_request = 4 | 0 | | | /// /// 5. Acquire Token silent with force_refresh = true /// Current_request = 4 | ATS_ID, 1 | , , 1, , , , 1 - /// Last_request = 4 | 0 | | | - /// /// [TestMethod] public async Task TelemetryCacheRefreshTestAsync() @@ -221,7 +194,6 @@ public async Task TelemetryCacheRefreshTestAsync() /// /// Acquire token with serialized token cache successfully /// Current_request = 4 | ATC_ID, 0 | 1 - /// Last_request = 4 | 0 | | | /// [TestMethod] public async Task TelemetryTestSerializedTokenCacheAsync() @@ -390,6 +362,115 @@ await pca.AcquireTokenByUsernamePassword(TestConstants.s_scope, "username", Test } } + [TestMethod] + public async Task CallerSdkDetailsTestAsync() + { + using (_harness = CreateTestHarness()) + { + _harness.HttpManager.AddInstanceDiscoveryMockHandler(); + var requestHandler = _harness.HttpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(); + + var cca = CreateConfidentialClientApp(); + + await cca.AcquireTokenForClient(TestConstants.s_scope) + .WithExtraQueryParameters(new Dictionary { + { "caller-sdk-id", "testApiId" }, + { "caller-sdk-ver", "testSdkVersion"} }) + .ExecuteAsync().ConfigureAwait(false); + + AssertCurrentTelemetry( + requestHandler.ActualRequestMessage, + ApiIds.AcquireTokenForClient, + CacheRefreshReason.NoCachedAccessToken, + callerSdkId: "testApiId", + callerSdkVersion: "testSdkVersion"); + } + } + + [TestMethod] + public async Task CallerSdkDetails_ConstraintsTestAsync() + { + string callerSdkId = "testApiIdMoreThan10Chars"; + string callerSdkVersion = "testSdkVersionMoreThan20Chars"; + + using (_harness = CreateTestHarness()) + { + _harness.HttpManager.AddInstanceDiscoveryMockHandler(); + var requestHandler = _harness.HttpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(); + + var cca = CreateConfidentialClientApp(); + + await cca.AcquireTokenForClient(TestConstants.s_scope) + .WithExtraQueryParameters(new Dictionary { + { "caller-sdk-id", callerSdkId }, + { "caller-sdk-ver", callerSdkVersion } }) + .ExecuteAsync().ConfigureAwait(false); + + AssertCurrentTelemetry( + requestHandler.ActualRequestMessage, + ApiIds.AcquireTokenForClient, + CacheRefreshReason.NoCachedAccessToken, + callerSdkId: callerSdkId.Substring(0, Math.Min(callerSdkId.Length, Constants.CallerSdkIdMaxLength)), + callerSdkVersion: callerSdkVersion.Substring(0, Math.Min(callerSdkVersion.Length, Constants.CallerSdkVersionMaxLength))); + } + } + + [TestMethod] + public async Task CallerSdkDetailsWithClientNameTestAsync() + { + using (_harness = CreateTestHarness()) + { + _harness.HttpManager.AddInstanceDiscoveryMockHandler(); + var requestHandler = _harness.HttpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(); + + var cca = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) + .WithClientSecret(TestConstants.ClientSecret) + .WithClientName("testApiId") + .WithClientVersion("testSdkVersion") + .WithAuthority(TestConstants.AuthorityCommonTenant) + .WithHttpManager(_harness.HttpManager) + .BuildConcrete(); + + await cca.AcquireTokenForClient(TestConstants.s_scope) + .ExecuteAsync().ConfigureAwait(false); + + AssertCurrentTelemetry( + requestHandler.ActualRequestMessage, + ApiIds.AcquireTokenForClient, + CacheRefreshReason.NoCachedAccessToken, + callerSdkId: "testApiId", + callerSdkVersion: "testSdkVersion"); + } + } + + [TestMethod] + public async Task CallerSdkDetailsWithNullClientNameTestAsync() + { + using (_harness = CreateTestHarness()) + { + _harness.HttpManager.AddInstanceDiscoveryMockHandler(); + var requestHandler = _harness.HttpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(); + + var cca = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) + .WithClientSecret(TestConstants.ClientSecret) + .WithClientName(null) + .WithClientVersion(null) + .WithAuthority(TestConstants.AuthorityCommonTenant) + .WithHttpManager(_harness.HttpManager) + .BuildConcrete(); + + await cca.AcquireTokenForClient(TestConstants.s_scope) + .ExecuteAsync().ConfigureAwait(false); + + AssertCurrentTelemetry( + requestHandler.ActualRequestMessage, + ApiIds.AcquireTokenForClient, + CacheRefreshReason.NoCachedAccessToken, + callerSdkId: "", + callerSdkVersion: ""); + } + } + private PublicClientApplication CreatePublicClientApp(bool isLegacyCacheEnabled = true) { return PublicClientApplicationBuilder.Create(TestConstants.ClientId) @@ -617,7 +698,9 @@ private void AssertCurrentTelemetry( ApiIds apiId, CacheRefreshReason cacheInfo, bool isCacheSerialized = false, - bool isLegacyCacheEnabled = true) + bool isLegacyCacheEnabled = true, + string callerSdkId = "", + string callerSdkVersion = "") { string[] telemetryCategories = requestMessage.Headers.GetValues( TelemetryConstants.XClientCurrentTelemetry).Single().Split('|'); @@ -625,7 +708,7 @@ private void AssertCurrentTelemetry( Assert.AreEqual(3, telemetryCategories.Length); Assert.AreEqual(1, telemetryCategories[0].Split(',').Length); // version Assert.AreEqual(5, telemetryCategories[1].Split(',').Length); // api_id, cache_info, region_used, region_source, region_outcome - Assert.AreEqual(3, telemetryCategories[2].Split(',').Length); // platform_fields + Assert.AreEqual(5, telemetryCategories[2].Split(',').Length); // platform_fields Assert.AreEqual(TelemetryConstants.HttpTelemetrySchemaVersion.ToString(), telemetryCategories[0]); // version @@ -640,6 +723,10 @@ private void AssertCurrentTelemetry( Assert.AreEqual(isLegacyCacheEnabled ? "1" : "0", telemetryCategories[2].Split(',')[1]); // is_legacy_cache_enabled Assert.AreEqual(TokenType.Bearer.ToString("D"), telemetryCategories[2].Split(',')[2]); + + Assert.AreEqual(callerSdkId, telemetryCategories[2].Split(',')[3]); + + Assert.AreEqual(callerSdkVersion, telemetryCategories[2].Split(',')[4]); } } } diff --git a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs index d441921544..679cbc14a2 100644 --- a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs @@ -32,6 +32,9 @@ public class OTelInstrumentationTests : TestBase private static MeterProvider s_meterProvider; private readonly List _exportedMetrics = new(); + private const string callerSdkId = "123"; + private const string callerSdkVersion = "1.1.1.1"; + [TestCleanup] public override void TestCleanup() { @@ -81,6 +84,7 @@ public async Task ProactiveTokenRefresh_ValidResponse_ClientCredential_Async() _harness.HttpManager.AddAllMocks(TokenResponseType.Valid_ClientCredentials); AuthenticationResult result = await _cca.AcquireTokenForClient(TestConstants.s_scope) + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) .ExecuteAsync() .ConfigureAwait(false); // Assert @@ -95,6 +99,7 @@ public async Task ProactiveTokenRefresh_ValidResponse_ClientCredential_Async() // Act Trace.WriteLine("4. ATS - should perform an RT refresh"); result = await _cca.AcquireTokenForClient(TestConstants.s_scope) + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) .ExecuteAsync() .ConfigureAwait(false); @@ -110,8 +115,9 @@ public async Task ProactiveTokenRefresh_ValidResponse_ClientCredential_Async() Trace.WriteLine("5. ATS - should not perform an RT refresh, as the token is still valid"); result = await _cca.AcquireTokenForClient(TestConstants.s_scope) - .ExecuteAsync() - .ConfigureAwait(false); + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) + .ExecuteAsync() + .ConfigureAwait(false); Trace.WriteLine(result.AuthenticationResultMetadata.DurationTotalInMs); @@ -151,6 +157,7 @@ public async Task ProactiveTokenRefresh_ValidResponse_MSI_Async() ManagedIdentitySource.AppService); AuthenticationResult result = await mi.AcquireTokenForManagedIdentity(resource) + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) .ExecuteAsync() .ConfigureAwait(false); @@ -167,6 +174,7 @@ public async Task ProactiveTokenRefresh_ValidResponse_MSI_Async() // Act Trace.WriteLine("4. ATM - should perform an RT refresh"); result = await mi.AcquireTokenForManagedIdentity(resource) + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) .ExecuteAsync() .ConfigureAwait(false); @@ -183,6 +191,7 @@ public async Task ProactiveTokenRefresh_ValidResponse_MSI_Async() Assert.AreEqual(refreshOn, result.AuthenticationResultMetadata.RefreshOn); result = await mi.AcquireTokenForManagedIdentity(resource) + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) .ExecuteAsync() .ConfigureAwait(false); @@ -214,6 +223,7 @@ public async Task ProactiveTokenRefresh_ValidResponse_OBO_Async() string oboCacheKey = "obo-cache-key"; var result = await cca.InitiateLongRunningProcessInWebApi(TestConstants.s_scope, TestConstants.DefaultAccessToken, ref oboCacheKey) + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) .ExecuteAsync().ConfigureAwait(false); Assert.AreEqual(TestConstants.ATSecret, result.AccessToken); @@ -225,7 +235,9 @@ public async Task ProactiveTokenRefresh_ValidResponse_OBO_Async() httpManager.AddSuccessTokenResponseMockHandlerForPost(); Trace.WriteLine("3. Configure AAD to respond with a valid token"); - result = await cca.AcquireTokenInLongRunningProcess(TestConstants.s_scope, oboCacheKey).ExecuteAsync().ConfigureAwait(false); + result = await cca.AcquireTokenInLongRunningProcess(TestConstants.s_scope, oboCacheKey) + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) + .ExecuteAsync().ConfigureAwait(false); Assert.AreEqual(TestConstants.ATSecret, result.AccessToken); Assert.AreEqual(TokenSource.Cache, result.AuthenticationResultMetadata.TokenSource); @@ -235,7 +247,9 @@ public async Task ProactiveTokenRefresh_ValidResponse_OBO_Async() Thread.Sleep(1000); Trace.WriteLine("4. Fetch token from cache"); - result = await cca.AcquireTokenInLongRunningProcess(TestConstants.s_scope, oboCacheKey).ExecuteAsync().ConfigureAwait(false); + result = await cca.AcquireTokenInLongRunningProcess(TestConstants.s_scope, oboCacheKey) + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) + .ExecuteAsync().ConfigureAwait(false); Assert.AreEqual(TestConstants.ATSecret, result.AccessToken); Assert.AreEqual(TokenSource.Cache, result.AuthenticationResultMetadata.TokenSource); @@ -268,6 +282,7 @@ public async Task ProactiveTokenRefresh_AadUnavailableResponse_Async() // Act AuthenticationResult result = await _cca.AcquireTokenForClient(TestConstants.s_scope) + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) .ExecuteAsync() .ConfigureAwait(false); @@ -281,6 +296,7 @@ public async Task ProactiveTokenRefresh_AadUnavailableResponse_Async() _harness.HttpManager.AddTokenResponse(TokenResponseType.Valid_ClientCredentials); result = await _cca.AcquireTokenForClient(TestConstants.s_scope) + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) .ExecuteAsync() .ConfigureAwait(false); Assert.IsNotNull(result); @@ -301,11 +317,13 @@ private async Task AcquireTokenSuccessAsync() // Acquire token for client with scope var result = await _cca.AcquireTokenForClient(TestConstants.s_scope) + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) .ExecuteAsync(CancellationToken.None).ConfigureAwait(false); Assert.IsNotNull(result); // Acquire token from the cache result = await _cca.AcquireTokenForClient(TestConstants.s_scope) + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) .ExecuteAsync(CancellationToken.None).ConfigureAwait(false); Assert.IsNotNull(result); } @@ -317,6 +335,7 @@ private async Task AcquireTokenMsalServiceExceptionAsync() //Test for MsalServiceException MsalServiceException ex = await AssertException.TaskThrowsAsync( () => _cca.AcquireTokenForClient(TestConstants.s_scopeForAnotherResource) + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) .WithTenantId(TestConstants.Utid) .ExecuteAsync(CancellationToken.None)).ConfigureAwait(false); @@ -329,6 +348,7 @@ private async Task AcquireTokenMsalClientExceptionAsync() //Test for MsalClientException MsalClientException exClient = await AssertException.TaskThrowsAsync( () => _cca.AcquireTokenForClient(null) // null scope -> client exception + .WithExtraQueryParameters(new Dictionary { { "caller-sdk-id", callerSdkId }, { "caller-sdk-ver", callerSdkVersion } }) .WithTenantId(TestConstants.Utid) .ExecuteAsync(CancellationToken.None)).ConfigureAwait(false); @@ -366,6 +386,8 @@ private void VerifyMetrics(int expectedMetricCount, List exportedMetrics expectedTags.Add(TelemetryConstants.MsalVersion); expectedTags.Add(TelemetryConstants.Platform); expectedTags.Add(TelemetryConstants.ApiId); + expectedTags.Add(TelemetryConstants.CallerSdkId); + expectedTags.Add(TelemetryConstants.CallerSdkVersion); expectedTags.Add(TelemetryConstants.TokenSource); expectedTags.Add(TelemetryConstants.CacheRefreshReason); expectedTags.Add(TelemetryConstants.CacheLevel); @@ -374,7 +396,7 @@ private void VerifyMetrics(int expectedMetricCount, List exportedMetrics foreach (var metricPoint in exportedItem.GetMetricPoints()) { totalSuccessfulRequests += metricPoint.GetSumLong(); - AssertTags(metricPoint.Tags, expectedTags); + AssertTags(metricPoint.Tags, expectedTags, true); } Assert.AreEqual(expectedSuccessfulRequests, totalSuccessfulRequests); @@ -388,13 +410,15 @@ private void VerifyMetrics(int expectedMetricCount, List exportedMetrics expectedTags.Add(TelemetryConstants.Platform); expectedTags.Add(TelemetryConstants.ErrorCode); expectedTags.Add(TelemetryConstants.ApiId); + expectedTags.Add(TelemetryConstants.CallerSdkId); + expectedTags.Add(TelemetryConstants.CallerSdkVersion); expectedTags.Add(TelemetryConstants.CacheRefreshReason); long totalFailedRequests = 0; foreach (var metricPoint in exportedItem.GetMetricPoints()) { totalFailedRequests += metricPoint.GetSumLong(); - AssertTags(metricPoint.Tags, expectedTags); + AssertTags(metricPoint.Tags, expectedTags, true); } Assert.AreEqual(expectedFailedRequests, totalFailedRequests); @@ -472,12 +496,10 @@ private void VerifyMetrics(int expectedMetricCount, List exportedMetrics Assert.Fail("Unexpected metrics logged."); break; } - - } } - private void AssertTags(ReadOnlyTagCollection tags, List expectedTags) + private void AssertTags(ReadOnlyTagCollection tags, List expectedTags, bool expectCallerSdkDetails = false) { Assert.AreEqual(expectedTags.Count, tags.Count); IDictionary tagDictionary = new Dictionary(); @@ -487,6 +509,12 @@ private void AssertTags(ReadOnlyTagCollection tags, List expectedTags) tagDictionary[tag.Key] = tag.Value; } + if (expectCallerSdkDetails) + { + Assert.AreEqual(callerSdkId, tagDictionary[TelemetryConstants.CallerSdkId]); + Assert.AreEqual(callerSdkVersion, tagDictionary[TelemetryConstants.CallerSdkVersion]); + } + foreach (var expectedTag in expectedTags) { Assert.IsNotNull(tagDictionary[expectedTag], $"Tag {expectedTag} is missing."); diff --git a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/RegionalTelemetryTests.cs b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/RegionalTelemetryTests.cs index 7fca1acdb0..4ddda3f382 100644 --- a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/RegionalTelemetryTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/RegionalTelemetryTests.cs @@ -346,7 +346,7 @@ private static void AssertCurrentTelemetry( Assert.AreEqual(3, telemetryCategories.Length); Assert.AreEqual(1, telemetryCategories[0].Split(',').Length); // version Assert.AreEqual(5, telemetryCategories[1].Split(',').Length); // api_id, cache_info, region_used, region_source, region_outcome - Assert.AreEqual(3, telemetryCategories[2].Split(',').Length); // platform_fields + Assert.AreEqual(5, telemetryCategories[2].Split(',').Length); // platform_fields Assert.AreEqual(TelemetryConstants.HttpTelemetrySchemaVersion.ToString(), telemetryCategories[0]); // version