diff --git a/src/http/httpClient/Middleware/AuthorizationHandler.cs b/src/http/httpClient/Middleware/AuthorizationHandler.cs index 4716b3c..602a575 100644 --- a/src/http/httpClient/Middleware/AuthorizationHandler.cs +++ b/src/http/httpClient/Middleware/AuthorizationHandler.cs @@ -15,92 +15,92 @@ namespace Microsoft.Kiota.Http.HttpClientLibrary.Middleware { + /// + /// Adds an Authorization header to the request if the header is not already present. + /// Also handles Continuous Access Evaluation (CAE) claims challenges if the initial + /// token request was made using this handler + /// + public class AuthorizationHandler : DelegatingHandler + { + + private const string AuthorizationHeader = "Authorization"; + private readonly BaseBearerTokenAuthenticationProvider authenticationProvider; + /// - /// Adds an Authorization header to the request if the header is not already present. - /// Also handles Continuous Access Evaluation (CAE) claims challenges if the initial - /// token request was made using this handler + /// Constructs an /// - public class AuthorizationHandler : DelegatingHandler + /// + /// + public AuthorizationHandler(BaseBearerTokenAuthenticationProvider authenticationProvider) { + if(authenticationProvider == null) throw new ArgumentNullException(nameof(authenticationProvider)); + this.authenticationProvider = authenticationProvider; + } - private const string AuthorizationHeader = "Authorization"; - private readonly BaseBearerTokenAuthenticationProvider authenticationProvider; - - /// - /// Constructs an - /// - /// - /// - public AuthorizationHandler(BaseBearerTokenAuthenticationProvider authenticationProvider) - { - if(authenticationProvider == null) throw new ArgumentNullException(nameof(authenticationProvider)); - this.authenticationProvider = authenticationProvider; - } + /// + /// Adds an Authorization header if not already provided + /// + /// + /// + /// + protected override async Task SendAsync(HttpRequestMessage request, + CancellationToken cancellationToken) + { + if(request == null) throw new ArgumentNullException(nameof(request)); - /// - /// Adds an Authorization header if not already provided - /// - /// - /// - /// - protected override async Task SendAsync(HttpRequestMessage request, - CancellationToken cancellationToken) + Activity? activity = null; + if(request.GetRequestOption() is { } obsOptions) + { + var activitySource = ActivitySourceRegistry.DefaultInstance.GetOrCreateActivitySource(obsOptions.TracerInstrumentationName); + activity = activitySource?.StartActivity($"{nameof(AuthorizationHandler)}_{nameof(SendAsync)}"); + activity?.SetTag("com.microsoft.kiota.handler.authorization.enable", true); + } + try + { + if(request.Headers.Contains(AuthorizationHeader)) { - if(request == null) throw new ArgumentNullException(nameof(request)); - - Activity? activity = null; - if(request.GetRequestOption() is { } obsOptions) - { - var activitySource = ActivitySourceRegistry.DefaultInstance.GetOrCreateActivitySource(obsOptions.TracerInstrumentationName); - activity = activitySource?.StartActivity($"{nameof(AuthorizationHandler)}_{nameof(SendAsync)}"); - activity?.SetTag("com.microsoft.kiota.handler.authorization.enable", true); - } - try - { - if(request.Headers.Contains(AuthorizationHeader)) - { - activity?.SetTag("com.microsoft.kiota.handler.authorization.token_present", true); - return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); - } - Dictionary additionalAuthenticationContext = new Dictionary(); - await AuthenticateRequestAsync(request, additionalAuthenticationContext, cancellationToken, activity).ConfigureAwait(false); - var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); - if(response.StatusCode != HttpStatusCode.Unauthorized || response.RequestMessage == null || !response.RequestMessage.IsBuffered()) - return response; - // Attempt CAE claims challenge - var claims = ContinuousAccessEvaluation.GetClaims(response); - if(string.IsNullOrEmpty(claims)) - return response; - activity?.AddEvent(new ActivityEvent("com.microsoft.kiota.handler.authorization.challenge_received")); - additionalAuthenticationContext[ContinuousAccessEvaluation.ClaimsKey] = claims; - HttpRequestMessage retryRequest = response.RequestMessage; - await AuthenticateRequestAsync(retryRequest, additionalAuthenticationContext, cancellationToken, activity).ConfigureAwait(false); - activity?.SetTag("http.request.resend_count", 1); - return await base.SendAsync(retryRequest, cancellationToken).ConfigureAwait(false); - } - finally - { - activity?.Dispose(); - } + activity?.SetTag("com.microsoft.kiota.handler.authorization.token_present", true); + return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); } + Dictionary additionalAuthenticationContext = new Dictionary(); + await AuthenticateRequestAsync(request, additionalAuthenticationContext, cancellationToken, activity).ConfigureAwait(false); + var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); + if(response.StatusCode != HttpStatusCode.Unauthorized || response.RequestMessage == null || !response.RequestMessage.IsBuffered()) + return response; + // Attempt CAE claims challenge + var claims = ContinuousAccessEvaluation.GetClaims(response); + if(string.IsNullOrEmpty(claims)) + return response; + activity?.AddEvent(new ActivityEvent("com.microsoft.kiota.handler.authorization.challenge_received")); + additionalAuthenticationContext[ContinuousAccessEvaluation.ClaimsKey] = claims; + HttpRequestMessage retryRequest = response.RequestMessage; + await AuthenticateRequestAsync(retryRequest, additionalAuthenticationContext, cancellationToken, activity).ConfigureAwait(false); + activity?.SetTag("http.request.resend_count", 1); + return await base.SendAsync(retryRequest, cancellationToken).ConfigureAwait(false); + } + finally + { + activity?.Dispose(); + } + } - private async Task AuthenticateRequestAsync(HttpRequestMessage request, - Dictionary additionalAuthenticationContext, - CancellationToken cancellationToken, - Activity? activityForAttributes) - { - var accessTokenProvider = authenticationProvider.AccessTokenProvider; - if(request.RequestUri == null || !accessTokenProvider.AllowedHostsValidator.IsUrlHostValid( - request.RequestUri)) - { - return; - } - var accessToken = await accessTokenProvider.GetAuthorizationTokenAsync( - request.RequestUri, - additionalAuthenticationContext, cancellationToken).ConfigureAwait(false); - activityForAttributes?.SetTag("com.microsoft.kiota.handler.authorization.token_obtained", true); - if(string.IsNullOrEmpty(accessToken)) return; - request.Headers.TryAddWithoutValidation(AuthorizationHeader, $"Bearer {accessToken}"); - } + private async Task AuthenticateRequestAsync(HttpRequestMessage request, + Dictionary additionalAuthenticationContext, + CancellationToken cancellationToken, + Activity? activityForAttributes) + { + var accessTokenProvider = authenticationProvider.AccessTokenProvider; + if(request.RequestUri == null || !accessTokenProvider.AllowedHostsValidator.IsUrlHostValid( + request.RequestUri)) + { + return; + } + var accessToken = await accessTokenProvider.GetAuthorizationTokenAsync( + request.RequestUri, + additionalAuthenticationContext, cancellationToken).ConfigureAwait(false); + activityForAttributes?.SetTag("com.microsoft.kiota.handler.authorization.token_obtained", true); + if(string.IsNullOrEmpty(accessToken)) return; + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); } + } } diff --git a/tests/http/httpClient/Middleware/AuthorizationHandlerTests.cs b/tests/http/httpClient/Middleware/AuthorizationHandlerTests.cs index a1ae87b..ba6ca84 100644 --- a/tests/http/httpClient/Middleware/AuthorizationHandlerTests.cs +++ b/tests/http/httpClient/Middleware/AuthorizationHandlerTests.cs @@ -1,6 +1,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Text; using Microsoft.Kiota.Abstractions.Authentication; using Microsoft.Kiota.Http.HttpClientLibrary.Middleware; using Microsoft.Kiota.Http.HttpClientLibrary.Tests.Mocks; @@ -12,9 +13,6 @@ namespace Microsoft.Kiota.Http.HttpClientLibrary.Tests.Middleware public class AuthorizationHandlerTests : IDisposable { private readonly MockRedirectHandler _testHttpMessageHandler; - - private IAccessTokenProvider _mockAccessTokenProvider; - private readonly string _expectedAccessToken = "token"; private readonly string _expectedAccessTokenAfterCAE = "token2"; @@ -34,14 +32,14 @@ public AuthorizationHandlerTests() It.IsAny(), It.IsAny>(), It.IsAny() - )).Returns(new Task(() => _expectedAccessToken)) - .Returns(new Task(() => _expectedAccessTokenAfterCAE)); + ).Result).Returns(_expectedAccessToken) + .Returns(_expectedAccessTokenAfterCAE); mockAccessTokenProvider.Setup(x => x.AllowedHostsValidator).Returns( - new AllowedHostsValidator(new List { "https://graph.microsoft.com" }) + new AllowedHostsValidator(new List { "graph.microsoft.com" }) ); - this._mockAccessTokenProvider = mockAccessTokenProvider.Object; - this._authenticationProvider = new BaseBearerTokenAuthenticationProvider(_mockAccessTokenProvider!); + var mockAuthenticationProvider = new Mock(mockAccessTokenProvider.Object); + this._authenticationProvider = mockAuthenticationProvider.Object; this._authorizationHandler = new AuthorizationHandler(_authenticationProvider) { InnerHandler = this._testHttpMessageHandler @@ -98,11 +96,12 @@ public async Task AuthorizationHandlerShouldAttemptCAEClaimsChallenge() { // Arrange HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com"); + httpRequestMessage.Content = new ByteArrayContent(Encoding.UTF8.GetBytes("test")); HttpResponseMessage httpResponse = new HttpResponseMessage(HttpStatusCode.Unauthorized); httpResponse.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Bearer", _claimsChallengeHeaderValue)); - this._testHttpMessageHandler.SetHttpResponse(httpResponse);// set the mock response + this._testHttpMessageHandler.SetHttpResponse(httpResponse, new HttpResponseMessage(HttpStatusCode.OK));// set the mock response // Act HttpResponseMessage response = await this._invoker.SendAsync(httpRequestMessage, new CancellationToken()); @@ -112,6 +111,7 @@ public async Task AuthorizationHandlerShouldAttemptCAEClaimsChallenge() Assert.True(response.RequestMessage.Headers.Contains("Authorization")); Assert.True(response.RequestMessage.Headers.GetValues("Authorization").Count() == 1); Assert.Equal($"Bearer {_expectedAccessTokenAfterCAE}", response.RequestMessage.Headers.GetValues("Authorization").First()); + Assert.Equal("test", await response.RequestMessage.Content!.ReadAsStringAsync()); } } }