diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/IdHelper.cs b/src/Microsoft.Identity.Web.Diagnostics/IdHelper.cs similarity index 80% rename from src/Microsoft.Identity.Web.TokenAcquisition/IdHelper.cs rename to src/Microsoft.Identity.Web.Diagnostics/IdHelper.cs index b8601e59e..9b6426cd4 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/IdHelper.cs +++ b/src/Microsoft.Identity.Web.Diagnostics/IdHelper.cs @@ -10,6 +10,8 @@ namespace Microsoft.Identity.Web { internal static class IdHelper { + private const string IDWebSku = "IDWeb."; + private static readonly Lazy s_idWebVersion = new Lazy( () => { @@ -34,9 +36,14 @@ internal static class IdHelper return version[1]; }); - public static string CreateTelemetryInfo() + internal static string GetIdWebVersion() + { + return s_idWebVersion.Value; + } + + internal static string CreateTelemetryInfo() { - return string.Format(CultureInfo.InvariantCulture, Constants.IDWebSku + s_idWebVersion.Value); + return string.Format(CultureInfo.InvariantCulture, IDWebSku + s_idWebVersion.Value); } } } diff --git a/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net462/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net462/InternalAPI.Unshipped.txt index e69de29bb..9077d1a55 100644 --- a/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net462/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net462/InternalAPI.Unshipped.txt @@ -0,0 +1,6 @@ +Microsoft.Identity.Web.Diagnostics.IdHelper +Microsoft.Identity.Web.IdHelper +static Microsoft.Identity.Web.Diagnostics.IdHelper.CreateTelemetryInfo() -> string! +static Microsoft.Identity.Web.Diagnostics.IdHelper.GetIdWebVersion() -> string! +static Microsoft.Identity.Web.IdHelper.CreateTelemetryInfo() -> string! +static Microsoft.Identity.Web.IdHelper.GetIdWebVersion() -> string! diff --git a/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net472/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net472/InternalAPI.Unshipped.txt index e69de29bb..ff830bd0f 100644 --- a/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net472/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net472/InternalAPI.Unshipped.txt @@ -0,0 +1,6 @@ +Microsoft.Identity.Web.Diagnostics.IdHelper +Microsoft.Identity.Web.IdHelper +static Microsoft.Identity.Web.Diagnostics.IdHelper.CreateTelemetryInfo() -> string! +static Microsoft.Identity.Web.Diagnostics.IdHelper.GetIdWebVersion() -> string! +static Microsoft.Identity.Web.IdHelper.CreateTelemetryInfo() -> string! +static Microsoft.Identity.Web.IdHelper.GetIdWebVersion() -> string! \ No newline at end of file diff --git a/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net6.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net6.0/InternalAPI.Unshipped.txt index e69de29bb..9077d1a55 100644 --- a/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net6.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net6.0/InternalAPI.Unshipped.txt @@ -0,0 +1,6 @@ +Microsoft.Identity.Web.Diagnostics.IdHelper +Microsoft.Identity.Web.IdHelper +static Microsoft.Identity.Web.Diagnostics.IdHelper.CreateTelemetryInfo() -> string! +static Microsoft.Identity.Web.Diagnostics.IdHelper.GetIdWebVersion() -> string! +static Microsoft.Identity.Web.IdHelper.CreateTelemetryInfo() -> string! +static Microsoft.Identity.Web.IdHelper.GetIdWebVersion() -> string! diff --git a/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net7.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net7.0/InternalAPI.Unshipped.txt index e69de29bb..9077d1a55 100644 --- a/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net7.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net7.0/InternalAPI.Unshipped.txt @@ -0,0 +1,6 @@ +Microsoft.Identity.Web.Diagnostics.IdHelper +Microsoft.Identity.Web.IdHelper +static Microsoft.Identity.Web.Diagnostics.IdHelper.CreateTelemetryInfo() -> string! +static Microsoft.Identity.Web.Diagnostics.IdHelper.GetIdWebVersion() -> string! +static Microsoft.Identity.Web.IdHelper.CreateTelemetryInfo() -> string! +static Microsoft.Identity.Web.IdHelper.GetIdWebVersion() -> string! diff --git a/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net8.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net8.0/InternalAPI.Unshipped.txt index e69de29bb..9077d1a55 100644 --- a/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net8.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net8.0/InternalAPI.Unshipped.txt @@ -0,0 +1,6 @@ +Microsoft.Identity.Web.Diagnostics.IdHelper +Microsoft.Identity.Web.IdHelper +static Microsoft.Identity.Web.Diagnostics.IdHelper.CreateTelemetryInfo() -> string! +static Microsoft.Identity.Web.Diagnostics.IdHelper.GetIdWebVersion() -> string! +static Microsoft.Identity.Web.IdHelper.CreateTelemetryInfo() -> string! +static Microsoft.Identity.Web.IdHelper.GetIdWebVersion() -> string! diff --git a/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net9.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net9.0/InternalAPI.Unshipped.txt index e69de29bb..9077d1a55 100644 --- a/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net9.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/net9.0/InternalAPI.Unshipped.txt @@ -0,0 +1,6 @@ +Microsoft.Identity.Web.Diagnostics.IdHelper +Microsoft.Identity.Web.IdHelper +static Microsoft.Identity.Web.Diagnostics.IdHelper.CreateTelemetryInfo() -> string! +static Microsoft.Identity.Web.Diagnostics.IdHelper.GetIdWebVersion() -> string! +static Microsoft.Identity.Web.IdHelper.CreateTelemetryInfo() -> string! +static Microsoft.Identity.Web.IdHelper.GetIdWebVersion() -> string! diff --git a/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt index e69de29bb..9077d1a55 100644 --- a/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.Diagnostics/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt @@ -0,0 +1,6 @@ +Microsoft.Identity.Web.Diagnostics.IdHelper +Microsoft.Identity.Web.IdHelper +static Microsoft.Identity.Web.Diagnostics.IdHelper.CreateTelemetryInfo() -> string! +static Microsoft.Identity.Web.Diagnostics.IdHelper.GetIdWebVersion() -> string! +static Microsoft.Identity.Web.IdHelper.CreateTelemetryInfo() -> string! +static Microsoft.Identity.Web.IdHelper.GetIdWebVersion() -> string! diff --git a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs index b0dccca0e..d55d89def 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs +++ b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; @@ -167,8 +168,8 @@ public Task CallApiForAppAsync( HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, false, null, user, cancellationToken).ConfigureAwait(false); return await DeserializeOutputAsync(response, effectiveOptions).ConfigureAwait(false); - } - + } + #if NET8_0_OR_GREATER /// public async Task CallApiForUserAsync( @@ -267,7 +268,7 @@ public Task CallApiForAppAsync( /// /// Named configuration. /// Delegate to override the configuration. - internal /* for tests */ DownstreamApiOptions MergeOptions( + internal /* for tests */ DownstreamApiOptions MergeOptions( string? optionsInstanceName, Action? calledApiOptionsOverride) { @@ -293,7 +294,7 @@ public Task CallApiForAppAsync( /// Named configuration. /// Delegate to override the configuration. /// Http method overriding the configuration options. - internal /* for tests */ DownstreamApiOptions MergeOptions( + internal /* for tests */ DownstreamApiOptions MergeOptions( string? optionsInstanceName, Action? calledApiOptionsOverride, HttpMethod httpMethod) { @@ -311,8 +312,8 @@ public Task CallApiForAppAsync( DownstreamApiOptionsReadOnlyHttpMethod clonedOptions = new DownstreamApiOptionsReadOnlyHttpMethod(options, httpMethod.ToString()); calledApiOptionsOverride?.Invoke(clonedOptions); return clonedOptions; - } - + } + internal static HttpContent? SerializeInput(TInput input, DownstreamApiOptions effectiveOptions) { return SerializeInputImpl(input, effectiveOptions, null); @@ -350,7 +351,7 @@ public Task CallApiForAppAsync( } internal static async Task DeserializeOutputAsync(HttpResponseMessage response, DownstreamApiOptions effectiveOptions) - where TOutput : class + where TOutput : class { try { @@ -398,8 +399,8 @@ public Task CallApiForAppAsync( } return stringContent as TOutput; } - } - + } + private static async Task DeserializeOutputImplAsync(HttpResponseMessage response, DownstreamApiOptions effectiveOptions, JsonTypeInfo outputJsonTypeInfo) where TOutput : class { @@ -451,7 +452,7 @@ public Task CallApiForAppAsync( } } - internal async Task CallApiInternalAsync( + internal /* for tests */ async Task CallApiInternalAsync( string? serviceName, DownstreamApiOptions effectiveOptions, bool appToken, @@ -494,7 +495,7 @@ internal async Task CallApiInternalAsync( return downstreamApiResult; } - internal async Task UpdateRequestAsync( + internal /* internal for test */ async Task UpdateRequestAsync( HttpRequestMessage httpRequestMessage, HttpContent? content, DownstreamApiOptions effectiveOptions, @@ -502,6 +503,8 @@ internal async Task UpdateRequestAsync( ClaimsPrincipal? user, CancellationToken cancellationToken) { + AddCallerSDKTelemetry(effectiveOptions); + if (content != null) { httpRequestMessage.Content = content; @@ -517,7 +520,7 @@ internal async Task UpdateRequestAsync( effectiveOptions.Scopes, effectiveOptions, user, - cancellationToken).ConfigureAwait(false); + cancellationToken).ConfigureAwait(false); httpRequestMessage.Headers.Add(Authorization, authorizationHeader); } @@ -532,5 +535,26 @@ internal async Task UpdateRequestAsync( // Opportunity to change the request message effectiveOptions.CustomizeHttpRequestMessage?.Invoke(httpRequestMessage); } + + internal /* for test */ static Dictionary CallerSDKDetails { get; } = new() + { + { "caller-sdk-id", "IdWeb_1" }, + { "caller-sdk-ver", IdHelper.GetIdWebVersion() } + }; + + private static void AddCallerSDKTelemetry(DownstreamApiOptions effectiveOptions) + { + if (effectiveOptions.AcquireTokenOptions.ExtraQueryParameters == null) + { + effectiveOptions.AcquireTokenOptions.ExtraQueryParameters = CallerSDKDetails; + } + else + { + effectiveOptions.AcquireTokenOptions.ExtraQueryParameters["caller-sdk-id"] = + CallerSDKDetails["caller-sdk-id"]; + effectiveOptions.AcquireTokenOptions.ExtraQueryParameters["caller-sdk-ver"] = + CallerSDKDetails["caller-sdk-ver"]; + } + } } } diff --git a/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net462/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net462/InternalAPI.Unshipped.txt index 1ffcfc6b9..066b4e1dc 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net462/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net462/InternalAPI.Unshipped.txt @@ -1 +1 @@ -bn +static Microsoft.Identity.Web.DownstreamApi.CallerSDKDetails.get -> System.Collections.Generic.Dictionary! \ No newline at end of file diff --git a/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net472/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net472/InternalAPI.Unshipped.txt index e69de29bb..066b4e1dc 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net472/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net472/InternalAPI.Unshipped.txt @@ -0,0 +1 @@ +static Microsoft.Identity.Web.DownstreamApi.CallerSDKDetails.get -> System.Collections.Generic.Dictionary! \ No newline at end of file diff --git a/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net6.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net6.0/InternalAPI.Unshipped.txt index e69de29bb..066b4e1dc 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net6.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net6.0/InternalAPI.Unshipped.txt @@ -0,0 +1 @@ +static Microsoft.Identity.Web.DownstreamApi.CallerSDKDetails.get -> System.Collections.Generic.Dictionary! \ No newline at end of file diff --git a/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net7.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net7.0/InternalAPI.Unshipped.txt index e69de29bb..066b4e1dc 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net7.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net7.0/InternalAPI.Unshipped.txt @@ -0,0 +1 @@ +static Microsoft.Identity.Web.DownstreamApi.CallerSDKDetails.get -> System.Collections.Generic.Dictionary! \ No newline at end of file diff --git a/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net8.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net8.0/InternalAPI.Unshipped.txt index e69de29bb..066b4e1dc 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net8.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net8.0/InternalAPI.Unshipped.txt @@ -0,0 +1 @@ +static Microsoft.Identity.Web.DownstreamApi.CallerSDKDetails.get -> System.Collections.Generic.Dictionary! \ No newline at end of file diff --git a/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net9.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net9.0/InternalAPI.Unshipped.txt index e69de29bb..066b4e1dc 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net9.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/net9.0/InternalAPI.Unshipped.txt @@ -0,0 +1 @@ +static Microsoft.Identity.Web.DownstreamApi.CallerSDKDetails.get -> System.Collections.Generic.Dictionary! \ No newline at end of file diff --git a/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt index e69de29bb..066b4e1dc 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.DownstreamApi/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt @@ -0,0 +1 @@ +static Microsoft.Identity.Web.DownstreamApi.CallerSDKDetails.get -> System.Collections.Generic.Dictionary! \ No newline at end of file diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/Constants.cs b/src/Microsoft.Identity.Web.TokenAcquisition/Constants.cs index 9734ee86a..2b4b08399 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/Constants.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/Constants.cs @@ -144,7 +144,6 @@ public static class Constants // Telemetry headers internal const string TelemetryHeaderKey = "x-client-brkrver"; - internal const string IDWebSku = "IDWeb."; // Authorize for scopes attributes internal const string XReturnUrl = "x-ReturnUrl"; diff --git a/tests/Microsoft.Identity.Web.Test/DownstreamWebApiSupport/DownstreamApiTests.cs b/tests/Microsoft.Identity.Web.Test/DownstreamWebApiSupport/DownstreamApiTests.cs index 052a96e42..1e0bce09a 100644 --- a/tests/Microsoft.Identity.Web.Test/DownstreamWebApiSupport/DownstreamApiTests.cs +++ b/tests/Microsoft.Identity.Web.Test/DownstreamWebApiSupport/DownstreamApiTests.cs @@ -49,14 +49,51 @@ public async Task UpdateRequestAsync_WithContent_AddsContentToRequestAsync() // Arrange var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "https://example.com"); var content = new StringContent("test content"); + var options = new DownstreamApiOptions(); // Act - await _input.UpdateRequestAsync(httpRequestMessage, content, new DownstreamApiOptions(), false, null, CancellationToken.None); + await _input.UpdateRequestAsync(httpRequestMessage, content, options, false, null, CancellationToken.None); // Assert Assert.Equal(content, httpRequestMessage.Content); Assert.Equal("application/json", httpRequestMessage.Headers.Accept.Single().MediaType); Assert.Equal("text/plain", httpRequestMessage.Content?.Headers.ContentType?.MediaType); + Assert.Equal(options.AcquireTokenOptions.ExtraQueryParameters, DownstreamApi.CallerSDKDetails); + } + + + [Fact] + public async Task UpdateRequestAsync_AddsToExtraQP() + { + // Arrange + var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "https://example.com"); + var content = new StringContent("test content"); + var options = new DownstreamApiOptions() { + AcquireTokenOptions = new AcquireTokenOptions() { + ExtraQueryParameters = new Dictionary() + { + { "n1", "v1" }, + { "n2", "v2" }, + { "caller-sdk-id", "bogus" } // value will be overwritten by the SDK + } + } }; + + // Act + await _input.UpdateRequestAsync(httpRequestMessage, content, options, false, null, CancellationToken.None); + + // Assert + Assert.Equal(content, httpRequestMessage.Content); + Assert.Equal("application/json", httpRequestMessage.Headers.Accept.Single().MediaType); + Assert.Equal("text/plain", httpRequestMessage.Content?.Headers.ContentType?.MediaType); + Assert.Equal("v1", options.AcquireTokenOptions.ExtraQueryParameters["n1"]); + Assert.Equal("v2", options.AcquireTokenOptions.ExtraQueryParameters["n2"]); + Assert.Equal( + DownstreamApi.CallerSDKDetails["caller-sdk-id"], + options.AcquireTokenOptions.ExtraQueryParameters["caller-sdk-id"] ); + Assert.Equal( + DownstreamApi.CallerSDKDetails["caller-sdk-ver"], + options.AcquireTokenOptions.ExtraQueryParameters["caller-sdk-ver"]); + } [Theory] @@ -83,6 +120,7 @@ public async Task UpdateRequestAsync_WithScopes_AddsAuthorizationHeaderToRequest Assert.Equal("ey", httpRequestMessage.Headers.Authorization?.Parameter); Assert.Equal("Bearer", httpRequestMessage.Headers.Authorization?.Scheme); Assert.Equal("application/json", httpRequestMessage.Headers.Accept.Single().MediaType); + Assert.Equal(options.AcquireTokenOptions.ExtraQueryParameters, DownstreamApi.CallerSDKDetails); } [Fact]