From e4b41f1add3db811c9be303f3485d769495f1163 Mon Sep 17 00:00:00 2001 From: Rune Gulbrandsen Date: Tue, 4 Jun 2024 11:06:03 +0200 Subject: [PATCH 1/2] Add disable access token cache config Added support for disabling access token cache configuration. --- README.md | 3 +- .../Configuration/OAuth2Configuration.cs | 5 +++ .../Helpers/OAuth2Provider.cs | 7 ++- ...tClientCredentialsAccessTokenAsyncTests.cs | 45 +++++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7624358..3ada81f 100644 --- a/README.md +++ b/README.md @@ -75,13 +75,14 @@ Authentication using OAuth2. ##### Client credentials -Using OAuth2 client credentials, all settings except `Scope` is required. +Using OAuth2 client credentials, all settings except `DisableTokenCache` and `Scope` is required. ``` "
": { "AuthenticationProvider": "OAuth2", "OAuth2": { "AuthorizationEndpoint": "", + "DisableTokenCache": false, "GrantType": "ClientCredentials", "Scope": "", "ClientCredentials": { diff --git a/src/HttpClientAuthentication/Configuration/OAuth2Configuration.cs b/src/HttpClientAuthentication/Configuration/OAuth2Configuration.cs index ad5dfc7..539b4a2 100644 --- a/src/HttpClientAuthentication/Configuration/OAuth2Configuration.cs +++ b/src/HttpClientAuthentication/Configuration/OAuth2Configuration.cs @@ -24,6 +24,11 @@ public sealed class OAuth2Configuration /// public string? AuthorizationScheme { get; set; } + /// + /// Gets or sets if the access token should be cached or not. + /// + public bool DisableTokenCache { get; set; } + /// /// Gets or sets the type of grant flow to be used. /// diff --git a/src/HttpClientAuthentication/Helpers/OAuth2Provider.cs b/src/HttpClientAuthentication/Helpers/OAuth2Provider.cs index 0fdb4ff..7299295 100644 --- a/src/HttpClientAuthentication/Helpers/OAuth2Provider.cs +++ b/src/HttpClientAuthentication/Helpers/OAuth2Provider.cs @@ -79,7 +79,12 @@ public OAuth2Provider(IHttpClientFactory clientFactory, ILogger return null; } - if (token.ExpiresIn > 0) + if (configuration.DisableTokenCache) + { + _logger.LogInformation("Token retrieved from {AuthorizationEndpoint} with client id {ClientId}, but the token cache is disabled.", + configuration.AuthorizationEndpoint, configuration.ClientCredentials!.ClientId); + } + else if (token.ExpiresIn > 0) { double cacheExpiresIn = (int)token.ExpiresIn * 0.95; _memoryCache.Set(cacheKey, token, TimeSpan.FromSeconds(cacheExpiresIn)); diff --git a/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/GetClientCredentialsAccessTokenAsyncTests.cs b/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/GetClientCredentialsAccessTokenAsyncTests.cs index 7c1d8ac..edfbd88 100644 --- a/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/GetClientCredentialsAccessTokenAsyncTests.cs +++ b/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/GetClientCredentialsAccessTokenAsyncTests.cs @@ -246,6 +246,51 @@ public async Task TestNoCachingOfAccessTokenResponseWithMissingExpiresIn() "https://somehost/", "client_id"), Times.Once); } + [Fact] + public async Task TestNoCachingOfAccessTokenResponseWhenCacheIsDiabled() + { + IServiceProvider services = BuildServices(); + + AccessTokenResponse expected = new() + { + AccessToken = "ACCESS_TOKEN", + TokenType = "TOKEN_TYPE", + ExpiresIn = null + }; + + Mock httpClientMock = services.GetRequiredService>(); + + httpClientMock.Setup(httpClient => httpClient.SendAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = JsonContent.Create(expected) + }); + + OAuth2Configuration configuration = new() + { + GrantType = OAuth2GrantType.ClientCredentials, + AuthorizationEndpoint = new("https://somehost/"), + ClientCredentials = new() + { + ClientId = "client_id", + ClientSecret = "client_secret" + }, + DisableTokenCache = true + }; + + OAuth2Provider provider = services.GetRequiredService(); + + await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + + Mock memoryCacheMock = services.GetRequiredService>(); + memoryCacheMock.Verify(memoryCache => memoryCache.CreateEntry("ClientCredentials#https://somehost/#client_id"), Times.Never); + + Mock> loggerMock = services.GetRequiredService>>(); + + loggerMock.VerifyExt(l => l.LogInformation("Token retrieved from {AuthorizationEndpoint} with client id {ClientId}, but the token cache is disabled.", + "https://somehost/", "client_id"), Times.Once); + } + [Fact] public async Task TestUseFormBasedAuthentication() { From 2b876b0190f33cdd50e15d46010eb59798411edd Mon Sep 17 00:00:00 2001 From: Rune Gulbrandsen Date: Tue, 4 Jun 2024 11:29:02 +0200 Subject: [PATCH 2/2] Update package to .NET 8 - Removed explicit .NET 7 support - Added .NET 8 support --- .editorconfig | 2 +- .github/workflows/master-pr.yml | 4 +- .github/workflows/master-publish.yml | 4 +- .github/workflows/master-push.yml | 4 +- LICENSE.txt | 2 +- src/HttpClientAuthentication/AssemblyInfo.cs | 2 +- .../Configuration/ApiKeyConfiguration.cs | 2 +- .../Configuration/BasicConfiguration.cs | 2 +- .../ClientCredentialsConfiguration.cs | 2 +- .../HttpClientAuthenticationConfiguration.cs | 2 +- .../Configuration/OAuth2Configuration.cs | 2 +- .../Constants/AuthenticationProvider.cs | 2 +- .../Constants/OAuth2GrantType.cs | 2 +- .../Constants/OAuth2Keyword.cs | 2 +- .../Handlers/ApiKeyAuthenticationHandler.cs | 2 +- .../Handlers/BaseAuthenticationHandler.cs | 2 +- .../Handlers/BasicAuthenticationHandler.cs | 2 +- .../Handlers/NoAuthenticationHandler.cs | 2 +- .../Handlers/OAuth2AuthenticationHandler.cs | 13 ++--- .../Helpers/AccessTokenResponse.cs | 2 +- .../Helpers/ErrorResponse.cs | 2 +- .../Helpers/IOAuth2Provider.cs | 2 +- .../Helpers/OAuth2Provider.cs | 48 ++++++++----------- .../HttpClientAuthentication.csproj | 27 ++++------- .../HttpClientAuthenticationExtensions.cs | 7 ++- .../AssemblyInfo.cs | 2 +- .../ApiKeyAuthenticationHandlerTests.cs | 14 +++--- .../BasicAuthenticationHandlerTests.cs | 14 +++--- .../Handlers/HandlerTestBase.cs | 2 +- .../OAuth2AuthenticationHandlerTests.cs | 16 +++---- ...tClientCredentialsAccessTokenAsyncTests.cs | 48 +++++++++---------- .../ParseResponseAsyncTests.cs | 10 ++-- .../Helpers/OAuth2ProviderTests/TestBase.cs | 2 +- .../TryParseAndLogOAuth2ErrorTests.cs | 12 ++--- .../HttpClientAuthentication.Test.csproj | 18 +++---- ...HttpClientAuthenticationExtensionsTests.cs | 6 +-- 36 files changed, 128 insertions(+), 159 deletions(-) diff --git a/.editorconfig b/.editorconfig index 5e70de5..74984b9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -156,7 +156,7 @@ dotnet_diagnostic.ca1819.severity = none dotnet_diagnostic.CA1848.severity = silent # CA2007: Consider calling ConfigureAwait on the awaited task -dotnet_diagnostic.CA2007.severity = suggestion +dotnet_diagnostic.CA2007.severity = none # IDE0058: Expression value is never used dotnet_diagnostic.IDE0058.severity = none diff --git a/.github/workflows/master-pr.yml b/.github/workflows/master-pr.yml index ca5f2c9..a52f43d 100644 --- a/.github/workflows/master-pr.yml +++ b/.github/workflows/master-pr.yml @@ -22,10 +22,10 @@ jobs: steps: - name: Checkout source uses: actions/checkout@v3 - - name: Setup .NET 7.0.x + - name: Setup .NET 8.0.x uses: actions/setup-dotnet@v3 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: Restore solution run: dotnet restore - name: Build solution diff --git a/.github/workflows/master-publish.yml b/.github/workflows/master-publish.yml index edd1137..6d55abe 100644 --- a/.github/workflows/master-publish.yml +++ b/.github/workflows/master-publish.yml @@ -16,10 +16,10 @@ jobs: echo "VERSION=${VERSION#v}" >> $GITHUB_ENV - name: Checkout source uses: actions/checkout@v3 - - name: Setup .NET 7.0.x + - name: Setup .NET 8.0.x uses: actions/setup-dotnet@v3 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: Restore solution run: dotnet restore - name: Build solution (pre-release) diff --git a/.github/workflows/master-push.yml b/.github/workflows/master-push.yml index e34dff1..fdc0031 100644 --- a/.github/workflows/master-push.yml +++ b/.github/workflows/master-push.yml @@ -19,10 +19,10 @@ jobs: steps: - name: Checkout source uses: actions/checkout@v3 - - name: Setup .NET 7.0.x + - name: Setup .NET 8.0.x uses: actions/setup-dotnet@v3 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: Restore solution run: dotnet restore - name: Build solution diff --git a/LICENSE.txt b/LICENSE.txt index e1436e2..df47f43 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2023 Rune Gulbrandsen +Copyright (c) 2024 Rune Gulbrandsen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without diff --git a/src/HttpClientAuthentication/AssemblyInfo.cs b/src/HttpClientAuthentication/AssemblyInfo.cs index e2fea29..9be9855 100644 --- a/src/HttpClientAuthentication/AssemblyInfo.cs +++ b/src/HttpClientAuthentication/AssemblyInfo.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using System.Runtime.CompilerServices; diff --git a/src/HttpClientAuthentication/Configuration/ApiKeyConfiguration.cs b/src/HttpClientAuthentication/Configuration/ApiKeyConfiguration.cs index 996de8d..17133ac 100644 --- a/src/HttpClientAuthentication/Configuration/ApiKeyConfiguration.cs +++ b/src/HttpClientAuthentication/Configuration/ApiKeyConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. namespace KISS.HttpClientAuthentication.Configuration diff --git a/src/HttpClientAuthentication/Configuration/BasicConfiguration.cs b/src/HttpClientAuthentication/Configuration/BasicConfiguration.cs index ca4514f..df82c67 100644 --- a/src/HttpClientAuthentication/Configuration/BasicConfiguration.cs +++ b/src/HttpClientAuthentication/Configuration/BasicConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. namespace KISS.HttpClientAuthentication.Configuration diff --git a/src/HttpClientAuthentication/Configuration/ClientCredentialsConfiguration.cs b/src/HttpClientAuthentication/Configuration/ClientCredentialsConfiguration.cs index 9a7d0f1..d858720 100644 --- a/src/HttpClientAuthentication/Configuration/ClientCredentialsConfiguration.cs +++ b/src/HttpClientAuthentication/Configuration/ClientCredentialsConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. namespace KISS.HttpClientAuthentication.Configuration diff --git a/src/HttpClientAuthentication/Configuration/HttpClientAuthenticationConfiguration.cs b/src/HttpClientAuthentication/Configuration/HttpClientAuthenticationConfiguration.cs index f88cc6f..bf6d9d8 100644 --- a/src/HttpClientAuthentication/Configuration/HttpClientAuthenticationConfiguration.cs +++ b/src/HttpClientAuthentication/Configuration/HttpClientAuthenticationConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using KISS.HttpClientAuthentication.Constants; diff --git a/src/HttpClientAuthentication/Configuration/OAuth2Configuration.cs b/src/HttpClientAuthentication/Configuration/OAuth2Configuration.cs index 539b4a2..6486ffb 100644 --- a/src/HttpClientAuthentication/Configuration/OAuth2Configuration.cs +++ b/src/HttpClientAuthentication/Configuration/OAuth2Configuration.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using KISS.HttpClientAuthentication.Constants; diff --git a/src/HttpClientAuthentication/Constants/AuthenticationProvider.cs b/src/HttpClientAuthentication/Constants/AuthenticationProvider.cs index 0c5583f..eec6f79 100644 --- a/src/HttpClientAuthentication/Constants/AuthenticationProvider.cs +++ b/src/HttpClientAuthentication/Constants/AuthenticationProvider.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. namespace KISS.HttpClientAuthentication.Constants diff --git a/src/HttpClientAuthentication/Constants/OAuth2GrantType.cs b/src/HttpClientAuthentication/Constants/OAuth2GrantType.cs index 077a5e7..e4e091c 100644 --- a/src/HttpClientAuthentication/Constants/OAuth2GrantType.cs +++ b/src/HttpClientAuthentication/Constants/OAuth2GrantType.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. namespace KISS.HttpClientAuthentication.Constants diff --git a/src/HttpClientAuthentication/Constants/OAuth2Keyword.cs b/src/HttpClientAuthentication/Constants/OAuth2Keyword.cs index 78a6f6c..1f6120f 100644 --- a/src/HttpClientAuthentication/Constants/OAuth2Keyword.cs +++ b/src/HttpClientAuthentication/Constants/OAuth2Keyword.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. namespace KISS.HttpClientAuthentication.Constants diff --git a/src/HttpClientAuthentication/Handlers/ApiKeyAuthenticationHandler.cs b/src/HttpClientAuthentication/Handlers/ApiKeyAuthenticationHandler.cs index a1f77d8..fc9f93a 100644 --- a/src/HttpClientAuthentication/Handlers/ApiKeyAuthenticationHandler.cs +++ b/src/HttpClientAuthentication/Handlers/ApiKeyAuthenticationHandler.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using KISS.HttpClientAuthentication.Configuration; diff --git a/src/HttpClientAuthentication/Handlers/BaseAuthenticationHandler.cs b/src/HttpClientAuthentication/Handlers/BaseAuthenticationHandler.cs index bb0bb45..41099f6 100644 --- a/src/HttpClientAuthentication/Handlers/BaseAuthenticationHandler.cs +++ b/src/HttpClientAuthentication/Handlers/BaseAuthenticationHandler.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. namespace KISS.HttpClientAuthentication.Handlers diff --git a/src/HttpClientAuthentication/Handlers/BasicAuthenticationHandler.cs b/src/HttpClientAuthentication/Handlers/BasicAuthenticationHandler.cs index 4705e7a..c6cfe3a 100644 --- a/src/HttpClientAuthentication/Handlers/BasicAuthenticationHandler.cs +++ b/src/HttpClientAuthentication/Handlers/BasicAuthenticationHandler.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using System.Net.Http.Headers; diff --git a/src/HttpClientAuthentication/Handlers/NoAuthenticationHandler.cs b/src/HttpClientAuthentication/Handlers/NoAuthenticationHandler.cs index 863a593..dfe8eb7 100644 --- a/src/HttpClientAuthentication/Handlers/NoAuthenticationHandler.cs +++ b/src/HttpClientAuthentication/Handlers/NoAuthenticationHandler.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. namespace KISS.HttpClientAuthentication.Handlers diff --git a/src/HttpClientAuthentication/Handlers/OAuth2AuthenticationHandler.cs b/src/HttpClientAuthentication/Handlers/OAuth2AuthenticationHandler.cs index 4abfe26..f918acf 100644 --- a/src/HttpClientAuthentication/Handlers/OAuth2AuthenticationHandler.cs +++ b/src/HttpClientAuthentication/Handlers/OAuth2AuthenticationHandler.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using System.Net.Http.Headers; @@ -8,20 +8,13 @@ namespace KISS.HttpClientAuthentication.Handlers { - internal sealed class OAuth2AuthenticationHandler : BaseAuthenticationHandler + internal sealed class OAuth2AuthenticationHandler(IOAuth2Provider provider) : BaseAuthenticationHandler { - private readonly IOAuth2Provider _provider; - - public OAuth2AuthenticationHandler(IOAuth2Provider provider) - { - _provider = provider; - } - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { AccessTokenResponse? token = Configuration.GrantType switch { - OAuth2GrantType.ClientCredentials => await _provider.GetClientCredentialsAccessTokenAsync(Configuration, cancellationToken).ConfigureAwait(false), + OAuth2GrantType.ClientCredentials => await provider.GetClientCredentialsAccessTokenAsync(Configuration, cancellationToken).ConfigureAwait(false), OAuth2GrantType.None => throw new InvalidOperationException($"{nameof(Configuration.GrantType)} must be specified."), _ => throw new InvalidOperationException($"The {nameof(Configuration.GrantType)} {Configuration.GrantType} is not supported."), }; diff --git a/src/HttpClientAuthentication/Helpers/AccessTokenResponse.cs b/src/HttpClientAuthentication/Helpers/AccessTokenResponse.cs index 374c1d1..fe7dba7 100644 --- a/src/HttpClientAuthentication/Helpers/AccessTokenResponse.cs +++ b/src/HttpClientAuthentication/Helpers/AccessTokenResponse.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using System.Text.Json.Serialization; diff --git a/src/HttpClientAuthentication/Helpers/ErrorResponse.cs b/src/HttpClientAuthentication/Helpers/ErrorResponse.cs index 94ddd56..9065c72 100644 --- a/src/HttpClientAuthentication/Helpers/ErrorResponse.cs +++ b/src/HttpClientAuthentication/Helpers/ErrorResponse.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using System.Text.Json.Serialization; diff --git a/src/HttpClientAuthentication/Helpers/IOAuth2Provider.cs b/src/HttpClientAuthentication/Helpers/IOAuth2Provider.cs index c36c999..fa479c9 100644 --- a/src/HttpClientAuthentication/Helpers/IOAuth2Provider.cs +++ b/src/HttpClientAuthentication/Helpers/IOAuth2Provider.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using KISS.HttpClientAuthentication.Configuration; diff --git a/src/HttpClientAuthentication/Helpers/OAuth2Provider.cs b/src/HttpClientAuthentication/Helpers/OAuth2Provider.cs index 7299295..63dc494 100644 --- a/src/HttpClientAuthentication/Helpers/OAuth2Provider.cs +++ b/src/HttpClientAuthentication/Helpers/OAuth2Provider.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using System.Net; @@ -15,18 +15,10 @@ namespace KISS.HttpClientAuthentication.Helpers /// /// Implementation of . /// - internal sealed class OAuth2Provider : IOAuth2Provider + internal sealed class OAuth2Provider(IHttpClientFactory clientFactory, ILogger logger, IMemoryCache memoryCache) + : IOAuth2Provider { - private readonly HttpClient _client; - private readonly ILogger _logger; - private readonly IMemoryCache _memoryCache; - - public OAuth2Provider(IHttpClientFactory clientFactory, ILogger logger, IMemoryCache memoryCache) - { - _client = clientFactory.CreateClient(nameof(HttpClientAuthentication)); - _logger = logger; - _memoryCache = memoryCache; - } + private readonly HttpClient _client = clientFactory.CreateClient(nameof(HttpClientAuthentication)); /// public async ValueTask GetClientCredentialsAccessTokenAsync(OAuth2Configuration configuration, CancellationToken cancellationToken = default) @@ -56,15 +48,15 @@ public OAuth2Provider(IHttpClientFactory clientFactory, ILogger string cacheKey = $"{configuration.GrantType}#{configuration.AuthorizationEndpoint}#{configuration.ClientCredentials!.ClientId}"; - if (_memoryCache.TryGetValue(cacheKey, out AccessTokenResponse? token)) + if (memoryCache.TryGetValue(cacheKey, out AccessTokenResponse? token)) { - _logger.LogInformation("Token for {AuthorizationEndpoint} with client id {ClientId} found in cache, using this.", - configuration.AuthorizationEndpoint, configuration.ClientCredentials.ClientId); + logger.LogInformation("Token for {AuthorizationEndpoint} with client id {ClientId} found in cache, using this.", + configuration.AuthorizationEndpoint, configuration.ClientCredentials.ClientId); return token; } - _logger.LogDebug("Could not find existing token in cache, requesting token from endpoint {AuthorizationEndpoint} with client id {ClientId}.", - configuration.AuthorizationEndpoint, configuration.ClientCredentials.ClientId); + logger.LogDebug("Could not find existing token in cache, requesting token from endpoint {AuthorizationEndpoint} with client id {ClientId}.", + configuration.AuthorizationEndpoint, configuration.ClientCredentials.ClientId); using FormUrlEncodedContent requestContent = GetClientCredentialsContent(configuration.ClientCredentials!, configuration.Scope); @@ -81,21 +73,21 @@ public OAuth2Provider(IHttpClientFactory clientFactory, ILogger if (configuration.DisableTokenCache) { - _logger.LogInformation("Token retrieved from {AuthorizationEndpoint} with client id {ClientId}, but the token cache is disabled.", - configuration.AuthorizationEndpoint, configuration.ClientCredentials!.ClientId); + logger.LogInformation("Token retrieved from {AuthorizationEndpoint} with client id {ClientId}, but the token cache is disabled.", + configuration.AuthorizationEndpoint, configuration.ClientCredentials!.ClientId); } else if (token.ExpiresIn > 0) { double cacheExpiresIn = (int)token.ExpiresIn * 0.95; - _memoryCache.Set(cacheKey, token, TimeSpan.FromSeconds(cacheExpiresIn)); + memoryCache.Set(cacheKey, token, TimeSpan.FromSeconds(cacheExpiresIn)); - _logger.LogInformation("Token retrieved from {AuthorizationEndpoint} with client id {ClientId} and cached for {CacheExpiresIn} seconds.", - configuration.AuthorizationEndpoint, configuration.ClientCredentials!.ClientId, cacheExpiresIn); + logger.LogInformation("Token retrieved from {AuthorizationEndpoint} with client id {ClientId} and cached for {CacheExpiresIn} seconds.", + configuration.AuthorizationEndpoint, configuration.ClientCredentials!.ClientId, cacheExpiresIn); } else { - _logger.LogInformation("Token retrieved from {AuthorizationEndpoint} with client id {ClientId}, but not cached since it is missing expires_in information.", - configuration.AuthorizationEndpoint, configuration.ClientCredentials!.ClientId); + logger.LogInformation("Token retrieved from {AuthorizationEndpoint} with client id {ClientId}, but not cached since it is missing expires_in information.", + configuration.AuthorizationEndpoint, configuration.ClientCredentials!.ClientId); } return token; @@ -136,8 +128,8 @@ private static FormUrlEncodedContent GetClientCredentialsContent(ClientCredentia if (result.StatusCode != HttpStatusCode.BadRequest || !TryParseAndLogOAuth2Error(body, configuration.AuthorizationEndpoint, configuration.ClientCredentials!.ClientId)) { - _logger.LogError("Could not authenticate against {AuthorizationEndpoint}, the returned status code was {StatusCode}. Response body: {Body}.", - configuration.AuthorizationEndpoint, result.StatusCode, body); + logger.LogError("Could not authenticate against {AuthorizationEndpoint}, the returned status code was {StatusCode}. Response body: {Body}.", + configuration.AuthorizationEndpoint, result.StatusCode, body); } return null; @@ -147,7 +139,7 @@ private static FormUrlEncodedContent GetClientCredentialsContent(ClientCredentia if (token?.AccessToken is null) { - _logger.LogError("The result from {AuthorizationEndpoint} is not a valid OAuth2 result.", configuration.AuthorizationEndpoint); + logger.LogError("The result from {AuthorizationEndpoint} is not a valid OAuth2 result.", configuration.AuthorizationEndpoint); return null; } @@ -227,7 +219,7 @@ private bool TryParseAndLogOAuth2Error(string errorContent, Uri authorizationEnd logMessage.Append('.'); #pragma warning disable CA2254 // Template should be a static expression - _logger.LogError(logMessage.ToString()); + logger.LogError(logMessage.ToString()); #pragma warning restore CA2254 // Template should be a static expression return true; diff --git a/src/HttpClientAuthentication/HttpClientAuthentication.csproj b/src/HttpClientAuthentication/HttpClientAuthentication.csproj index f26a5cb..3785ed4 100644 --- a/src/HttpClientAuthentication/HttpClientAuthentication.csproj +++ b/src/HttpClientAuthentication/HttpClientAuthentication.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0;net7.0 + netstandard2.0;net6.0;net8.0 KISS.HttpClientAuthentication KISS.HttpClientAuthentication @@ -9,9 +9,9 @@ enable - 1.0.0 + 2.0.0 Rune Gulbrandsen - Copyright (c) 2023 Rune Gulbrandsen. All rights reserved. + Copyright (c) 2024 Rune Gulbrandsen. All rights reserved. Extension methods to apply authentication handling to HttpClient based on configuration from .NET configuration providers. @@ -20,18 +20,9 @@ LICENSE.txt README.md - true - true true snupkg - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - @@ -41,12 +32,12 @@ - - - - - - + + + + + + diff --git a/src/HttpClientAuthentication/HttpClientAuthenticationExtensions.cs b/src/HttpClientAuthentication/HttpClientAuthenticationExtensions.cs index c1afca0..d4f3771 100644 --- a/src/HttpClientAuthentication/HttpClientAuthenticationExtensions.cs +++ b/src/HttpClientAuthentication/HttpClientAuthenticationExtensions.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using KISS.HttpClientAuthentication.Configuration; @@ -50,6 +50,10 @@ public static IHttpClientBuilder AddAuthenticatedHttpMessageHandler(this IHttpCl /// public static IHttpClientBuilder AddAuthenticatedHttpMessageHandler(this IHttpClientBuilder builder, string configSection) { +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(configSection); +#else if (builder is null) { throw new ArgumentNullException(nameof(builder)); @@ -59,6 +63,7 @@ public static IHttpClientBuilder AddAuthenticatedHttpMessageHandler(this IHttpCl { throw new ArgumentNullException(nameof(configSection)); } +#endif builder.Services.AddMemoryCache(); diff --git a/test/HttpClientAuthentication.Test/AssemblyInfo.cs b/test/HttpClientAuthentication.Test/AssemblyInfo.cs index 6129e84..402c6a0 100644 --- a/test/HttpClientAuthentication.Test/AssemblyInfo.cs +++ b/test/HttpClientAuthentication.Test/AssemblyInfo.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. [assembly: CLSCompliant(false)] diff --git a/test/HttpClientAuthentication.Test/Handlers/ApiKeyAuthenticationHandlerTests.cs b/test/HttpClientAuthentication.Test/Handlers/ApiKeyAuthenticationHandlerTests.cs index ac077d7..1a835a2 100644 --- a/test/HttpClientAuthentication.Test/Handlers/ApiKeyAuthenticationHandlerTests.cs +++ b/test/HttpClientAuthentication.Test/Handlers/ApiKeyAuthenticationHandlerTests.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using FluentAssertions; @@ -15,7 +15,7 @@ public class ApiKeyAuthenticationHandlerTests : HandlerTestBase [InlineData(null)] [InlineData("")] [InlineData(" \t\r\n ")] - public async Task TestSendAsyncThrowsExceptionWhenApiKeyHeaderIsNotSpecified(string header) + public async Task TestSendAsyncThrowsExceptionWhenApiKeyHeaderIsNotSpecified(string? header) { IServiceProvider services = BuildServices("Test", new Dictionary { @@ -28,15 +28,14 @@ public async Task TestSendAsyncThrowsExceptionWhenApiKeyHeaderIsNotSpecified(str Func act = () => httpClient.GetAsync("https://somehost"); await act.Should().ThrowAsync() - .WithMessage("HTTP client configured to use ApiKey, but Header is not set in the ApiKey configuration.") - .ConfigureAwait(false); + .WithMessage("HTTP client configured to use ApiKey, but Header is not set in the ApiKey configuration."); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" \t\r\n ")] - public async Task TestSendAsyncThrowsExceptionWhenApiKeyValueIsNotSpecified(string value) + public async Task TestSendAsyncThrowsExceptionWhenApiKeyValueIsNotSpecified(string? value) { IServiceProvider services = BuildServices("Test", new Dictionary { @@ -50,8 +49,7 @@ public async Task TestSendAsyncThrowsExceptionWhenApiKeyValueIsNotSpecified(stri Func act = () => httpClient.GetAsync("https://somehost"); await act.Should().ThrowAsync() - .WithMessage("HTTP client configured to use ApiKey, but Value is not set in the ApiKey configuration.") - .ConfigureAwait(false); + .WithMessage("HTTP client configured to use ApiKey, but Value is not set in the ApiKey configuration."); } [Fact] @@ -67,7 +65,7 @@ public async Task TestSendAsyncSetsApiKeyCorrectly() HttpClient httpClient = services.GetRequiredService().CreateClient("Test"); - await httpClient.GetAsync("https://somehost").ConfigureAwait(false); + await httpClient.GetAsync("https://somehost"); Mock hmhMock = services.GetRequiredService>(); diff --git a/test/HttpClientAuthentication.Test/Handlers/BasicAuthenticationHandlerTests.cs b/test/HttpClientAuthentication.Test/Handlers/BasicAuthenticationHandlerTests.cs index bcc3cf8..53f1130 100644 --- a/test/HttpClientAuthentication.Test/Handlers/BasicAuthenticationHandlerTests.cs +++ b/test/HttpClientAuthentication.Test/Handlers/BasicAuthenticationHandlerTests.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using System.Text; @@ -16,7 +16,7 @@ public class BasicAuthenticationHandlerTests : HandlerTestBase [InlineData(null)] [InlineData("")] [InlineData(" \t\r\n ")] - public async Task TestSendAsyncThrowsExceptionWhenUsernameIsNotSpecified(string username) + public async Task TestSendAsyncThrowsExceptionWhenUsernameIsNotSpecified(string? username) { IServiceProvider services = BuildServices("Test", new Dictionary { @@ -29,8 +29,7 @@ public async Task TestSendAsyncThrowsExceptionWhenUsernameIsNotSpecified(string Func act = () => httpClient.GetAsync("https://somehost"); await act.Should().ThrowAsync() - .WithMessage("HTTP client configured to use basic authentication but Username is missing in configuration.") - .ConfigureAwait(false); + .WithMessage("HTTP client configured to use basic authentication but Username is missing in configuration."); } @@ -38,7 +37,7 @@ await act.Should().ThrowAsync() [InlineData(null)] [InlineData("")] [InlineData(" \t\r\n ")] - public async Task TestSendAsyncThrowsExceptionWhenPasswordIsNotSpecified(string password) + public async Task TestSendAsyncThrowsExceptionWhenPasswordIsNotSpecified(string? password) { IServiceProvider services = BuildServices("Test", new Dictionary { @@ -52,8 +51,7 @@ public async Task TestSendAsyncThrowsExceptionWhenPasswordIsNotSpecified(string Func act = () => httpClient.GetAsync("https://somehost"); await act.Should().ThrowAsync() - .WithMessage("HTTP client configured to use basic authentication but Password is missing in configuration.") - .ConfigureAwait(false); + .WithMessage("HTTP client configured to use basic authentication but Password is missing in configuration."); } @@ -70,7 +68,7 @@ public async Task TestSendAsyncSetsBasicAuthorizationCorrectly() HttpClient httpClient = services.GetRequiredService().CreateClient("Test"); - await httpClient.GetAsync("https://somehost").ConfigureAwait(false); + await httpClient.GetAsync("https://somehost"); Mock hmhMock = services.GetRequiredService>(); diff --git a/test/HttpClientAuthentication.Test/Handlers/HandlerTestBase.cs b/test/HttpClientAuthentication.Test/Handlers/HandlerTestBase.cs index f275eb7..61fa5dd 100644 --- a/test/HttpClientAuthentication.Test/Handlers/HandlerTestBase.cs +++ b/test/HttpClientAuthentication.Test/Handlers/HandlerTestBase.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using System.Net; diff --git a/test/HttpClientAuthentication.Test/Handlers/OAuth2AuthenticationHandlerTests.cs b/test/HttpClientAuthentication.Test/Handlers/OAuth2AuthenticationHandlerTests.cs index 134a175..9b7a7ba 100644 --- a/test/HttpClientAuthentication.Test/Handlers/OAuth2AuthenticationHandlerTests.cs +++ b/test/HttpClientAuthentication.Test/Handlers/OAuth2AuthenticationHandlerTests.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using FluentAssertions; @@ -27,8 +27,7 @@ public async Task TestSendAsyncThrowsExceptionWhenGrantTypeIsMissing() Func act = () => httpClient.GetAsync("https://somehost"); await act.Should().ThrowAsync() - .WithMessage("GrantType must be specified.") - .ConfigureAwait(false); + .WithMessage("GrantType must be specified."); } [Fact] @@ -45,8 +44,7 @@ public async Task TestSendAsyncThrowsExceptionWhenGrantTypeIsNone() Func act = () => httpClient.GetAsync("https://somehost"); await act.Should().ThrowAsync() - .WithMessage("GrantType must be specified.") - .ConfigureAwait(false); + .WithMessage("GrantType must be specified."); } [Fact] @@ -63,8 +61,7 @@ public async Task TestSendAsyncThrowsExceptionWhenGrantTypeIsInvalid() Func act = () => httpClient.GetAsync("https://somehost"); await act.Should().ThrowAsync() - .WithMessage("The GrantType 99 is not supported.") - .ConfigureAwait(false); + .WithMessage("The GrantType 99 is not supported."); } [Fact] @@ -81,8 +78,7 @@ public async Task TestSendAsyncThrowsExceptionWhenNoTokenIsReturnedFromOAuthProv Func act = () => httpClient.GetAsync("https://somehost"); await act.Should().ThrowAsync() - .WithMessage("HTTP client configured to use OAuth2 authentication, but no valid access token could be retrieved.") - .ConfigureAwait(false); + .WithMessage("HTTP client configured to use OAuth2 authentication, but no valid access token could be retrieved."); } [Fact] @@ -105,7 +101,7 @@ public async Task TestSendAsyncSetsAuthorizationHeaderCorrectly() HttpClient httpClient = services.GetRequiredService().CreateClient("Test"); - await httpClient.GetAsync("https://somehost").ConfigureAwait(false); + await httpClient.GetAsync("https://somehost"); Mock hmhMock = services.GetRequiredService>(); diff --git a/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/GetClientCredentialsAccessTokenAsyncTests.cs b/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/GetClientCredentialsAccessTokenAsyncTests.cs index edfbd88..a4428d5 100644 --- a/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/GetClientCredentialsAccessTokenAsyncTests.cs +++ b/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/GetClientCredentialsAccessTokenAsyncTests.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using System.Net; @@ -27,8 +27,7 @@ public async Task TestInvalidGrantTypeThrowsArgumentException() Func act = () => provider.GetClientCredentialsAccessTokenAsync(new(), default!).AsTask(); - await act.Should().ThrowAsync().WithParameterName("configuration").WithMessage("GrantType must be ClientCredentials.*") - .ConfigureAwait(false); + await act.Should().ThrowAsync().WithParameterName("configuration").WithMessage("GrantType must be ClientCredentials.*"); } [Fact] @@ -39,15 +38,14 @@ public async Task TestMissingClientCredentialsConfigurationSectionThrowsArgument Func act = () => provider.GetClientCredentialsAccessTokenAsync(new() { GrantType = OAuth2GrantType.ClientCredentials }, default!) .AsTask(); - await act.Should().ThrowAsync().WithParameterName("configuration").WithMessage("No valid ClientCredentials found.*") - .ConfigureAwait(false); + await act.Should().ThrowAsync().WithParameterName("configuration").WithMessage("No valid ClientCredentials found.*"); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" \t\r\n ")] - public async Task TestClientIdIsNullEmptyOrWhitespacesThrowsArgumentException(string clientId) + public async Task TestClientIdIsNullEmptyOrWhitespacesThrowsArgumentException(string? clientId) { IServiceProvider services = BuildServices(); @@ -59,7 +57,7 @@ public async Task TestClientIdIsNullEmptyOrWhitespacesThrowsArgumentException(st AuthorizationEndpoint = new("https://somehost/"), ClientCredentials = new() { - ClientId = clientId, + ClientId = clientId!, ClientSecret = "client_secret" } }; @@ -69,15 +67,14 @@ public async Task TestClientIdIsNullEmptyOrWhitespacesThrowsArgumentException(st await act.Should().ThrowAsync() .WithParameterName("configuration") - .WithMessage("ClientCredentials.ClientId must be specified.*") - .ConfigureAwait(false); + .WithMessage("ClientCredentials.ClientId must be specified.*"); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" \t\r\n ")] - public async Task TestClientSecretIsNullEmptyOrWhitespacesThrowsArgumentException(string clientSecret) + public async Task TestClientSecretIsNullEmptyOrWhitespacesThrowsArgumentException(string? clientSecret) { IServiceProvider services = BuildServices(); @@ -90,7 +87,7 @@ public async Task TestClientSecretIsNullEmptyOrWhitespacesThrowsArgumentExceptio ClientCredentials = new() { ClientId = "client_id", - ClientSecret = clientSecret + ClientSecret = clientSecret! } }; @@ -99,8 +96,7 @@ public async Task TestClientSecretIsNullEmptyOrWhitespacesThrowsArgumentExceptio await act.Should().ThrowAsync() .WithParameterName("configuration") - .WithMessage("ClientCredentials.ClientSecret must be specified.*") - .ConfigureAwait(false); + .WithMessage("ClientCredentials.ClientSecret must be specified.*"); } [Fact] @@ -132,7 +128,7 @@ public async Task TestCacheHitIsReturnedAsToken() } }; - AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default); result.Should().NotBeNull(); result!.AccessToken.Should().Be("Access_Token"); @@ -179,7 +175,7 @@ public async Task TestGetAndCacheAccessTokenResponse() OAuth2Provider provider = services.GetRequiredService(); - AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default); result.Should().NotBeNull(); @@ -235,7 +231,7 @@ public async Task TestNoCachingOfAccessTokenResponseWithMissingExpiresIn() OAuth2Provider provider = services.GetRequiredService(); - await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + await provider.GetClientCredentialsAccessTokenAsync(configuration, default); Mock memoryCacheMock = services.GetRequiredService>(); memoryCacheMock.Verify(memoryCache => memoryCache.CreateEntry("ClientCredentials#https://somehost/#client_id"), Times.Never); @@ -280,7 +276,7 @@ public async Task TestNoCachingOfAccessTokenResponseWhenCacheIsDiabled() OAuth2Provider provider = services.GetRequiredService(); - await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + await provider.GetClientCredentialsAccessTokenAsync(configuration, default); Mock memoryCacheMock = services.GetRequiredService>(); memoryCacheMock.Verify(memoryCache => memoryCache.CreateEntry("ClientCredentials#https://somehost/#client_id"), Times.Never); @@ -304,7 +300,7 @@ public async Task TestUseFormBasedAuthentication() request.Should().NotBeNull("Missing SendAsync request."); request.Headers.Authorization.Should().BeNull(); - string content = await request.Content!.ReadAsStringAsync(default).ConfigureAwait(false); + string content = await request.Content!.ReadAsStringAsync(default); content.Should().Be($"grant_type=client_credentials&client_id=client_id&client_secret=client_secret{Environment.NewLine}"); }) .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.NotFound)); @@ -322,7 +318,7 @@ public async Task TestUseFormBasedAuthentication() OAuth2Provider provider = services.GetRequiredService(); - await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + await provider.GetClientCredentialsAccessTokenAsync(configuration, default); } [Fact] @@ -346,7 +342,7 @@ public async Task TestUseBasicAuthentication() actualRequest.Headers.Authorization!.Parameter.Should().Be(Convert.ToBase64String(Encoding.ASCII.GetBytes($"client_id:client_secret"))); } - string content = await request.Content!.ReadAsStringAsync(default).ConfigureAwait(false); + string content = await request.Content!.ReadAsStringAsync(default); content.Should().Be($"grant_type=client_credentials{Environment.NewLine}"); }) .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.NotFound)); @@ -365,7 +361,7 @@ public async Task TestUseBasicAuthentication() OAuth2Provider provider = services.GetRequiredService(); - await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + await provider.GetClientCredentialsAccessTokenAsync(configuration, default); } [Fact] @@ -381,7 +377,7 @@ public async Task TestRequestContainsScopeWhenSpecified() request.Should().NotBeNull("Missing SendAsync request."); request.Headers.Authorization.Should().BeNull(); - string content = await request.Content!.ReadAsStringAsync(default).ConfigureAwait(false); + string content = await request.Content!.ReadAsStringAsync(default); content.Should().Be($"*scope=test_scope*"); }) .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.NotFound)); @@ -400,7 +396,7 @@ public async Task TestRequestContainsScopeWhenSpecified() OAuth2Provider provider = services.GetRequiredService(); - await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + await provider.GetClientCredentialsAccessTokenAsync(configuration, default); } [Theory] @@ -408,7 +404,7 @@ public async Task TestRequestContainsScopeWhenSpecified() [InlineData("")] [InlineData(" ")] [InlineData(" \t\r\n")] - public async Task TestRequestHasNoScopeWhenNullEmptyOrWhitespace(string scope) + public async Task TestRequestHasNoScopeWhenNullEmptyOrWhitespace(string? scope) { IServiceProvider services = BuildServices(); @@ -420,7 +416,7 @@ public async Task TestRequestHasNoScopeWhenNullEmptyOrWhitespace(string scope) request.Should().NotBeNull("Missing SendAsync request."); request.Headers.Authorization.Should().BeNull(); - string content = await request.Content!.ReadAsStringAsync(default).ConfigureAwait(false); + string content = await request.Content!.ReadAsStringAsync(default); content.Should().NotBe($"*scope=*"); }) .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.NotFound)); @@ -439,7 +435,7 @@ public async Task TestRequestHasNoScopeWhenNullEmptyOrWhitespace(string scope) OAuth2Provider provider = services.GetRequiredService(); - await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + await provider.GetClientCredentialsAccessTokenAsync(configuration, default); } } } diff --git a/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/ParseResponseAsyncTests.cs b/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/ParseResponseAsyncTests.cs index 59988a7..892780b 100644 --- a/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/ParseResponseAsyncTests.cs +++ b/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/ParseResponseAsyncTests.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using System.Net; @@ -49,7 +49,7 @@ public async Task TestSetsTokenTypeToBearerWhenNotSpecified() } }; - AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default); result.Should().BeEquivalentTo(expected, options => options.Using(ctx => ctx.Subject.Should().Match(f => f.TokenType == "Bearer")) @@ -90,7 +90,7 @@ public async Task TestUsesConfiguredAuthorizationSchemeAsTokenType() } }; - AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default); result.Should().BeEquivalentTo(expected, options => options.Using(ctx => ctx.Subject.Should().Match(f => f.TokenType == "Authorization_Scheme")) @@ -120,7 +120,7 @@ public async Task TestFailedRequestReturnsNullAndLogsError() OAuth2Provider provider = services.GetRequiredService(); - AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default); result.Should().BeNull(); @@ -156,7 +156,7 @@ public async Task TestInvalidResponseReturnsNullAndLogsError(string response) OAuth2Provider provider = services.GetRequiredService(); - AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default); result.Should().BeNull(); diff --git a/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/TestBase.cs b/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/TestBase.cs index 5747046..3304015 100644 --- a/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/TestBase.cs +++ b/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/TestBase.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using KISS.HttpClientAuthentication.Helpers; diff --git a/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/TryParseAndLogOAuth2ErrorTests.cs b/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/TryParseAndLogOAuth2ErrorTests.cs index f67be6f..63df3fe 100644 --- a/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/TryParseAndLogOAuth2ErrorTests.cs +++ b/test/HttpClientAuthentication.Test/Helpers/OAuth2ProviderTests/TryParseAndLogOAuth2ErrorTests.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using System.Net; @@ -47,7 +47,7 @@ public async Task TestErrorIsFullyParsedAndLogged() OAuth2Provider provider = services.GetRequiredService(); - AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default); result.Should().BeNull(); @@ -86,7 +86,7 @@ public async Task TestDescriptionIsSkippedWhenNotSpecified() OAuth2Provider provider = services.GetRequiredService(); - AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default); result.Should().BeNull(); @@ -125,7 +125,7 @@ public async Task TestUriIsSkippedWhenNotSpecified() OAuth2Provider provider = services.GetRequiredService(); - AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default); result.Should().BeNull(); @@ -164,7 +164,7 @@ public async Task TestDescriptionAndUriIsSkippedWhenNotSpecified() OAuth2Provider provider = services.GetRequiredService(); - AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default); result.Should().BeNull(); @@ -201,7 +201,7 @@ public async Task TestParsingIsSkippedOnInvalidErrorContent(string content) OAuth2Provider provider = services.GetRequiredService(); - AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default).ConfigureAwait(false); + AccessTokenResponse? result = await provider.GetClientCredentialsAccessTokenAsync(configuration, default); result.Should().BeNull(); diff --git a/test/HttpClientAuthentication.Test/HttpClientAuthentication.Test.csproj b/test/HttpClientAuthentication.Test/HttpClientAuthentication.Test.csproj index 46040bd..957b7f1 100644 --- a/test/HttpClientAuthentication.Test/HttpClientAuthentication.Test.csproj +++ b/test/HttpClientAuthentication.Test/HttpClientAuthentication.Test.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 KISS.HttpClientAuthentication.Test KISS.HttpClientAuthentication.Test @@ -14,21 +14,21 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/HttpClientAuthentication.Test/HttpClientAuthenticationExtensionsTests.cs b/test/HttpClientAuthentication.Test/HttpClientAuthenticationExtensionsTests.cs index f7d71f9..53967ec 100644 --- a/test/HttpClientAuthentication.Test/HttpClientAuthenticationExtensionsTests.cs +++ b/test/HttpClientAuthentication.Test/HttpClientAuthenticationExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright © 2023 Rune Gulbrandsen. +// Copyright © 2024 Rune Gulbrandsen. // All rights reserved. Licensed under the MIT License; see LICENSE.txt. using FluentAssertions; @@ -41,7 +41,7 @@ public void TestAddAuthenticatedHttpMessageHandlerThrowsArgumentNullExceptionWhe [Fact] public void TestGetAuthenticationHandlerThrowsInvalidOperationExceptionWhenConfigSectionIsMissing() { - IServiceProvider services = BuildServices(new Dictionary()); + IServiceProvider services = BuildServices([]); IHttpClientFactory factory = services.GetRequiredService(); @@ -182,7 +182,7 @@ public void TestGetAuthenticationHandlerReturnsOAuth2AuthenticationHandlerWhenAu handlerMock.Object.InnerHandler.Should().BeAssignableTo(); } - private static IServiceProvider BuildServices(IEnumerable> configuration) + private static ServiceProvider BuildServices(IEnumerable> configuration) { ServiceCollection services = new();