diff --git a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.cs b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.cs index 84022a689..601decd96 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.cs +++ b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.cs @@ -16,7 +16,7 @@ namespace Microsoft.Identity.Web { /// - internal partial class DownstreamApi : IDownstreamApi + partial class DownstreamApi : IDownstreamApi { /// public async Task GetForUserAsync( @@ -37,9 +37,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -73,9 +73,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -99,9 +99,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -134,9 +134,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -168,9 +168,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -204,9 +204,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -237,9 +237,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -272,9 +272,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -306,9 +306,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -342,9 +342,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -375,9 +375,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -410,9 +410,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -446,9 +446,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -482,9 +482,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -515,9 +515,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -550,9 +550,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -586,9 +586,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -622,9 +622,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -655,9 +655,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -690,9 +690,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -720,9 +720,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -758,9 +758,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -785,9 +785,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -822,9 +822,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -857,9 +857,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -895,9 +895,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -929,9 +929,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -966,9 +966,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -1001,9 +1001,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -1039,9 +1039,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -1073,9 +1073,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -1110,9 +1110,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -1145,9 +1145,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -1183,9 +1183,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -1217,9 +1217,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -1254,9 +1254,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -1289,9 +1289,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -1327,9 +1327,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -1361,9 +1361,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } @@ -1398,9 +1398,9 @@ ex is InvalidOperationException || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } diff --git a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.tt b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.tt index c57431c99..2e6f27a7e 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.tt +++ b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.tt @@ -22,7 +22,7 @@ using System.Text.Json.Serialization.Metadata; namespace Microsoft.Identity.Web { /// - internal partial class DownstreamApi : IDownstreamApi + partial class DownstreamApi : IDownstreamApi { <# bool firstMethod = true; @@ -67,7 +67,7 @@ namespace Microsoft.Identity.Web #> <# } - firstMethod = false; + firstMethod = false; #> /// public async <#= returnType #> <#= httpMethod #>For<#= token #>Async<#= template #>( @@ -113,7 +113,7 @@ namespace Microsoft.Identity.Web if (hasOutput && framework == "net8") { #> return await DeserializeOutputAsync(response, effectiveOptions, outputJsonTypeInfo).ConfigureAwait(false); -<# } +<# } else if (hasOutput) {#> return await DeserializeOutputAsync(response, effectiveOptions).ConfigureAwait(false); @@ -124,9 +124,9 @@ namespace Microsoft.Identity.Web || ex is HttpRequestException) { Logger.HttpRequestError( - _logger, + _logger, serviceName!, - effectiveOptions.BaseUrl!, + effectiveOptions.BaseUrl!, effectiveOptions.RelativePath!, ex); throw; } diff --git a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.Logger.cs b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.Logger.cs index b8cfa72de..6eac9353b 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.Logger.cs +++ b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.Logger.cs @@ -9,15 +9,15 @@ namespace Microsoft.Identity.Web /// /// LoggerMessage class for DownstreamApi. /// - internal partial class DownstreamApi + partial class DownstreamApi { internal static class Logger { private static readonly Action s_httpRequestError = LoggerMessage.Define( - LogLevel.Debug, - DownstreamApiLoggingEventId.HttpRequestError, - "[MsIdWeb] An error occurred during HTTP Request. " + + LogLevel.Debug, + DownstreamApiLoggingEventId.HttpRequestError, + "[MsIdWeb] An error occurred during HTTP Request. " + "ServiceName: {serviceName}, " + "BaseUrl: {BaseUrl}, " + "RelativePath: {RelativePath} "); @@ -25,7 +25,7 @@ internal static class Logger private static readonly Action s_unauthenticatedApiCall = LoggerMessage.Define( LogLevel.Information, - DownstreamApiLoggingEventId.UnauthenticatedApiCall, + DownstreamApiLoggingEventId.UnauthenticatedApiCall, "[MsIdWeb] An unauthenticated call was made to the Api with null Scopes"); @@ -38,10 +38,10 @@ internal static class Logger /// Relative path from appsettings. /// Exception. public static void HttpRequestError( - ILogger logger, + ILogger logger, string ServiceName, - string BaseUrl, - string RelativePath, + string BaseUrl, + string RelativePath, Exception? ex) => s_httpRequestError(logger, ServiceName, BaseUrl, RelativePath, ex); /// diff --git a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs index eb498f990..cfa8fb401 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs +++ b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; @@ -21,7 +21,7 @@ namespace Microsoft.Identity.Web { /// - internal partial class DownstreamApi : IDownstreamApi + public partial class DownstreamApi : IDownstreamApi { private readonly IAuthorizationHeaderProvider _authorizationHeaderProvider; private readonly IHttpClientFactory _httpClientFactory; @@ -169,20 +169,20 @@ 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( - string? serviceName, - TInput input, - JsonTypeInfo inputJsonTypeInfo, - JsonTypeInfo outputJsonTypeInfo, - Action? downstreamApiOptionsOverride = null, - ClaimsPrincipal? user = default, - CancellationToken cancellationToken = default) - where TOutput : class - { + /// + public async Task CallApiForUserAsync( + string? serviceName, + TInput input, + JsonTypeInfo inputJsonTypeInfo, + JsonTypeInfo outputJsonTypeInfo, + Action? downstreamApiOptionsOverride = null, + ClaimsPrincipal? user = default, + CancellationToken cancellationToken = default) + where TOutput : class + { DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); HttpContent? effectiveInput = SerializeInput(input, effectiveOptions, inputJsonTypeInfo); @@ -196,33 +196,33 @@ public Task CallApiForAppAsync( } return await DeserializeOutputAsync(response, effectiveOptions, outputJsonTypeInfo).ConfigureAwait(false); - } - - /// - public async Task CallApiForUserAsync( - string serviceName, - JsonTypeInfo outputJsonTypeInfo, - Action? downstreamApiOptionsOverride = null, - ClaimsPrincipal? user = default, - CancellationToken cancellationToken = default) - where TOutput : class - { + } + + /// + public async Task CallApiForUserAsync( + string serviceName, + JsonTypeInfo outputJsonTypeInfo, + Action? downstreamApiOptionsOverride = null, + ClaimsPrincipal? user = default, + CancellationToken cancellationToken = default) + where TOutput : class + { DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, false, null, user, cancellationToken).ConfigureAwait(false); - return await DeserializeOutputAsync(response, effectiveOptions, outputJsonTypeInfo).ConfigureAwait(false); - } - - /// - public async Task CallApiForAppAsync( - string? serviceName, - TInput input, - JsonTypeInfo inputJsonTypeInfo, - JsonTypeInfo outputJsonTypeInfo, - Action? downstreamApiOptionsOverride = null, - CancellationToken cancellationToken = default) - where TOutput : class - { + return await DeserializeOutputAsync(response, effectiveOptions, outputJsonTypeInfo).ConfigureAwait(false); + } + + /// + public async Task CallApiForAppAsync( + string? serviceName, + TInput input, + JsonTypeInfo inputJsonTypeInfo, + JsonTypeInfo outputJsonTypeInfo, + Action? downstreamApiOptionsOverride = null, + CancellationToken cancellationToken = default) + where TOutput : class + { DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); HttpContent? effectiveInput = SerializeInput(input, effectiveOptions, inputJsonTypeInfo); HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, true, @@ -234,42 +234,42 @@ public Task CallApiForAppAsync( effectiveInput?.Dispose(); } - return await DeserializeOutputAsync(response, effectiveOptions, outputJsonTypeInfo).ConfigureAwait(false); - } - - /// - public async Task CallApiForAppAsync( - string serviceName, - JsonTypeInfo outputJsonTypeInfo, - Action? downstreamApiOptionsOverride = null, - CancellationToken cancellationToken = default) - where TOutput : class - { + return await DeserializeOutputAsync(response, effectiveOptions, outputJsonTypeInfo).ConfigureAwait(false); + } + + /// + public async Task CallApiForAppAsync( + string serviceName, + JsonTypeInfo outputJsonTypeInfo, + Action? downstreamApiOptionsOverride = null, + CancellationToken cancellationToken = default) + where TOutput : class + { DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, true, null, null, cancellationToken).ConfigureAwait(false); - return await DeserializeOutputAsync(response, effectiveOptions, outputJsonTypeInfo).ConfigureAwait(false); - } - + return await DeserializeOutputAsync(response, effectiveOptions, outputJsonTypeInfo).ConfigureAwait(false); + } + internal static HttpContent? SerializeInput(TInput input, DownstreamApiOptions effectiveOptions, JsonTypeInfo inputJsonTypeInfo) { - return SerializeInputImpl(input, effectiveOptions, inputJsonTypeInfo); - } - + return SerializeInputImpl(input, effectiveOptions, inputJsonTypeInfo); + } + internal static async Task DeserializeOutputAsync(HttpResponseMessage response, DownstreamApiOptions effectiveOptions, JsonTypeInfo outputJsonTypeInfo) where TOutput : class - { - return await DeserializeOutputImplAsync(response, effectiveOptions, outputJsonTypeInfo); - } + { + return await DeserializeOutputImplAsync(response, effectiveOptions, outputJsonTypeInfo); + } #endif - + /// /// Merge the options from configuration and override from caller. /// /// Named configuration. /// Delegate to override the configuration. - internal /* for tests */ DownstreamApiOptions MergeOptions( + internal /* for tests */ DownstreamApiOptions MergeOptions( string? optionsInstanceName, Action? calledApiOptionsOverride) { @@ -295,7 +295,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) { @@ -313,47 +313,47 @@ 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); + return SerializeInputImpl(input, effectiveOptions, null); } private static HttpContent? SerializeInputImpl(TInput input, DownstreamApiOptions effectiveOptions, JsonTypeInfo? inputJsonTypeInfo = null) { - HttpContent? httpContent; - - if (effectiveOptions.Serializer != null) + HttpContent? httpContent; + + if (effectiveOptions.Serializer != null) { - httpContent = effectiveOptions.Serializer(input); + httpContent = effectiveOptions.Serializer(input); } else { - // if the input is already an HttpContent, it's used as is, and should already contain a ContentType. - httpContent = input switch - { - HttpContent content => content, - string str when !string.IsNullOrEmpty(effectiveOptions.ContentType) && effectiveOptions.ContentType.StartsWith("text", StringComparison.OrdinalIgnoreCase) => new StringContent(str), - string str => new StringContent( - inputJsonTypeInfo == null ? JsonSerializer.Serialize(str) : JsonSerializer.Serialize(str, inputJsonTypeInfo), - Encoding.UTF8, - "application/json"), - byte[] bytes => new ByteArrayContent(bytes), - Stream stream => new StreamContent(stream), - null => null, - _ => new StringContent( - inputJsonTypeInfo == null ? JsonSerializer.Serialize(input) : JsonSerializer.Serialize(input, inputJsonTypeInfo), - Encoding.UTF8, - "application/json"), - }; + // if the input is already an HttpContent, it's used as is, and should already contain a ContentType. + httpContent = input switch + { + HttpContent content => content, + string str when !string.IsNullOrEmpty(effectiveOptions.ContentType) && effectiveOptions.ContentType.StartsWith("text", StringComparison.OrdinalIgnoreCase) => new StringContent(str), + string str => new StringContent( + inputJsonTypeInfo == null ? JsonSerializer.Serialize(str) : JsonSerializer.Serialize(str, inputJsonTypeInfo), + Encoding.UTF8, + "application/json"), + byte[] bytes => new ByteArrayContent(bytes), + Stream stream => new StreamContent(stream), + null => null, + _ => new StringContent( + inputJsonTypeInfo == null ? JsonSerializer.Serialize(input) : JsonSerializer.Serialize(input, inputJsonTypeInfo), + Encoding.UTF8, + "application/json"), + }; } return httpContent; } internal static async Task DeserializeOutputAsync(HttpResponseMessage response, DownstreamApiOptions effectiveOptions) - where TOutput : class - { + where TOutput : class + { try { response.EnsureSuccessStatusCode(); @@ -382,16 +382,16 @@ public Task CallApiForAppAsync( { return effectiveOptions.Deserializer(content) as TOutput; } - else if (typeof(TOutput).IsAssignableFrom(typeof(HttpContent))) - { - return content as TOutput; + else if (typeof(TOutput).IsAssignableFrom(typeof(HttpContent))) + { + return content as TOutput; } else { string stringContent = await content.ReadAsStringAsync(); if (mediaType == "application/json") { - return JsonSerializer.Deserialize(stringContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + return JsonSerializer.Deserialize(stringContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); } if (mediaType != null && !mediaType.StartsWith("text/", StringComparison.OrdinalIgnoreCase)) { @@ -399,7 +399,7 @@ public Task CallApiForAppAsync( throw new NotSupportedException("Content type not supported. Provide your own deserializer. "); } return stringContent as TOutput; - } + } } private static async Task DeserializeOutputImplAsync(HttpResponseMessage response, DownstreamApiOptions effectiveOptions, JsonTypeInfo outputJsonTypeInfo) @@ -433,9 +433,9 @@ public Task CallApiForAppAsync( { return effectiveOptions.Deserializer(content) as TOutput; } - else if (typeof(TOutput).IsAssignableFrom(typeof(HttpContent))) - { - return content as TOutput; + else if (typeof(TOutput).IsAssignableFrom(typeof(HttpContent))) + { + return content as TOutput; } else { @@ -453,7 +453,7 @@ public Task CallApiForAppAsync( } } - internal /* for tests */ async Task CallApiInternalAsync( + protected virtual /* for tests */ async Task CallApiInternalAsync( string? serviceName, DownstreamApiOptions effectiveOptions, bool appToken, @@ -464,106 +464,106 @@ public Task CallApiForAppAsync( // Downstream API URI string apiUrl = effectiveOptions.GetApiUrl(); - // Create an HTTP request message - using HttpRequestMessage httpRequestMessage = new( + // Create an HTTP request message + using HttpRequestMessage httpRequestMessage = new( new HttpMethod(effectiveOptions.HttpMethod), apiUrl); await UpdateRequestAsync(httpRequestMessage, content, effectiveOptions, appToken, user, cancellationToken); - using HttpClient client = string.IsNullOrEmpty(serviceName) ? _httpClientFactory.CreateClient() : _httpClientFactory.CreateClient(serviceName); - - // Send the HTTP message - var downstreamApiResult = await client.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false); - - // Retry only if the resource sent 401 Unauthorized with WWW-Authenticate header and claims - if (downstreamApiResult.StatusCode == System.Net.HttpStatusCode.Unauthorized) - { - effectiveOptions.AcquireTokenOptions.Claims = WwwAuthenticateParameters.GetClaimChallengeFromResponseHeaders(downstreamApiResult.Headers); - - if (!string.IsNullOrEmpty(effectiveOptions.AcquireTokenOptions.Claims)) - { - using HttpRequestMessage retryHttpRequestMessage = new( - new HttpMethod(effectiveOptions.HttpMethod), - apiUrl); - - await UpdateRequestAsync(retryHttpRequestMessage, content, effectiveOptions, appToken, user, cancellationToken); - - return await client.SendAsync(retryHttpRequestMessage, cancellationToken).ConfigureAwait(false); - } - } - - return downstreamApiResult; - } - - internal /* internal for test */ async Task UpdateRequestAsync( - HttpRequestMessage httpRequestMessage, - HttpContent? content, - DownstreamApiOptions effectiveOptions, - bool appToken, - ClaimsPrincipal? user, - CancellationToken cancellationToken) - { - AddCallerSDKTelemetry(effectiveOptions); - - if (content != null) - { - httpRequestMessage.Content = content; - } - - effectiveOptions.RequestAppToken = appToken; - + using HttpClient client = string.IsNullOrEmpty(serviceName) ? _httpClientFactory.CreateClient() : _httpClientFactory.CreateClient(serviceName); + + // Send the HTTP message + var downstreamApiResult = await client.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false); + + // Retry only if the resource sent 401 Unauthorized with WWW-Authenticate header and claims + if (downstreamApiResult.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + effectiveOptions.AcquireTokenOptions.Claims = WwwAuthenticateParameters.GetClaimChallengeFromResponseHeaders(downstreamApiResult.Headers); + + if (!string.IsNullOrEmpty(effectiveOptions.AcquireTokenOptions.Claims)) + { + using HttpRequestMessage retryHttpRequestMessage = new( + new HttpMethod(effectiveOptions.HttpMethod), + apiUrl); + + await UpdateRequestAsync(retryHttpRequestMessage, content, effectiveOptions, appToken, user, cancellationToken); + + return await client.SendAsync(retryHttpRequestMessage, cancellationToken).ConfigureAwait(false); + } + } + + return downstreamApiResult; + } + + internal /* internal for test */ async Task UpdateRequestAsync( + HttpRequestMessage httpRequestMessage, + HttpContent? content, + DownstreamApiOptions effectiveOptions, + bool appToken, + ClaimsPrincipal? user, + CancellationToken cancellationToken) + { + AddCallerSDKTelemetry(effectiveOptions); + + if (content != null) + { + httpRequestMessage.Content = content; + } + + effectiveOptions.RequestAppToken = appToken; + // Obtention of the authorization header (except when calling an anonymous endpoint // which is done by not specifying any scopes if (effectiveOptions.Scopes != null && effectiveOptions.Scopes.Any()) { - string authorizationHeader = await _authorizationHeaderProvider.CreateAuthorizationHeaderAsync( - effectiveOptions.Scopes, - effectiveOptions, - user, - cancellationToken).ConfigureAwait(false); - - if (authorizationHeader.StartsWith(AuthSchemeDstsSamlBearer, StringComparison.OrdinalIgnoreCase)) - { - // TryAddWithoutValidation method bypasses strict validation, allowing non-standard headers to be added for custom Header schemes that cannot be parsed. - httpRequestMessage.Headers.TryAddWithoutValidation(Authorization, authorizationHeader); - } - else - { - httpRequestMessage.Headers.Add(Authorization, authorizationHeader); - } + string authorizationHeader = await _authorizationHeaderProvider.CreateAuthorizationHeaderAsync( + effectiveOptions.Scopes, + effectiveOptions, + user, + cancellationToken).ConfigureAwait(false); + + if (authorizationHeader.StartsWith(AuthSchemeDstsSamlBearer, StringComparison.OrdinalIgnoreCase)) + { + // TryAddWithoutValidation method bypasses strict validation, allowing non-standard headers to be added for custom Header schemes that cannot be parsed. + httpRequestMessage.Headers.TryAddWithoutValidation(Authorization, authorizationHeader); + } + else + { + httpRequestMessage.Headers.Add(Authorization, authorizationHeader); + } } else { Logger.UnauthenticatedApiCall(_logger, null); } if (!string.IsNullOrEmpty(effectiveOptions.AcceptHeader)) - { - httpRequestMessage.Headers.Accept.ParseAdd(effectiveOptions.AcceptHeader); + { + httpRequestMessage.Headers.Accept.ParseAdd(effectiveOptions.AcceptHeader); } // Opportunity to change the request message - effectiveOptions.CustomizeHttpRequestMessage?.Invoke(httpRequestMessage); + 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"]; + } } - - 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"]; - } - } } }